diff --git a/.config/.prettierignore b/.config/.prettierignore index 9245daa3e..b939d452b 100644 --- a/.config/.prettierignore +++ b/.config/.prettierignore @@ -12,6 +12,3 @@ ../example/docs/ **/tmp **/.vs - -# Remove once Prettier has support -../src/test/converter2/behavior/resolutionMode.ts diff --git a/.config/typedoc.json b/.config/typedoc.json index d0186774a..609bee4b7 100644 --- a/.config/typedoc.json +++ b/.config/typedoc.json @@ -13,6 +13,8 @@ ], "sort": ["kind", "instance-first", "required-first", "alphabetical"], "entryPoints": ["../src/index.ts"], + "alwaysCreateEntryPointModule": false, + "projectDocuments": ["../internal-docs/plugins.md"], "excludeExternals": true, "excludeInternal": false, "excludePrivate": true, @@ -44,5 +46,10 @@ "includeGroups": false }, "includeVersion": true, - "logLevel": "Verbose" + "logLevel": "Verbose", + "externalSymbolLinkMappings": { + "@types/markdown-it": { + "MarkdownIt": "https://markdown-it.github.io/markdown-it/#MarkdownIt" + } + } } diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index b923486a1..000000000 --- a/.eslintrc +++ /dev/null @@ -1,88 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.json" - }, - "env": { - "node": true, - "es6": true - }, - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ], - "ignorePatterns": [ - "src/test/renderer/specs", - "dist", - "docs", - "tmp", - "coverage", - "static/main.js", - "src/lib/output/themes/default/assets", - "node_modules", - // Would be nice to lint these, but they shouldn't be included in the project, - // so we need a second eslint config file. - "example", - "src/test/converter", - "src/test/converter2", - "src/test/module", - "src/test/packages", - "src/test/renderer", - "src/test/slow/entry-points", - "scripts", - "bin" - ], - "rules": { - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/await-thenable": "error", - - // This one is just annoying since it complains at incomplete code - "no-empty": "off", - - // This rule is factually incorrect. Interfaces which extend some type alias can be used to introduce - // new type names. This is useful particularly when dealing with mixins. - "@typescript-eslint/no-empty-interface": "off", - - // We still use `any` fairly frequently... - "@typescript-eslint/ban-types": "off", - "@typescript-eslint/no-explicit-any": "off", - - // Really annoying, doesn't provide any value. - "@typescript-eslint/no-empty-function": "off", - - // Declaration merging with a namespace is a necessary tool when working with enums. - "@typescript-eslint/no-namespace": "off", - - // Reported by TypeScript - "@typescript-eslint/no-unused-vars": "off", - - "no-console": "warn", - - // Feel free to turn one of these back on and submit a PR! - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - - "no-restricted-syntax": [ - "warn", - { - "selector": "ImportDeclaration[source.value=/.*perf$/]", - "message": "Benchmark calls must be removed before committing." - }, - { - "selector": "MemberExpression[object.name=type][property.name=symbol]", - "message": "Use type.getSymbol() instead, Type.symbol is not properly typed." - } - ] - }, - "overrides": [ - { - "files": ["src/test/**"], - "env": { - "mocha": true - } - } - ] -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index aa503e5d8..708307afb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,9 @@ labels: bug Note: Turn off skipErrorChecks before reporting a crash. Bug reports for crashes with that option on are out of scope. -If possible, please create a *minimal* repo reproducing your problem and link it. -You can easily do this by submitting a pull request to https://github.com/TypeStrong/typedoc-repros +If possible, please create a *minimal* repo reproducing your problem. +If it is more than a single small file, please submit a pull request to +https://github.com/TypeStrong/typedoc-repros which changes the files necessary to reproduce your bug. If this is not possible, include at least: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f05ed0295..feeb99d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,13 +5,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ["18", "20", "21"] + node: ["18", "20", "22"] name: Node ${{ matrix.node }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - name: Install @@ -20,24 +20,40 @@ jobs: run: npm run build - name: Test run: npm run test:full - env: - # For pull requests, this gets handled by the visual-regression workflow. - # For other test runs, skip so we don't have to go build the regression screenshots. - SKIP_VISUAL_TEST: true - name: Lint run: npm run lint -- --max-warnings 0 - name: Circular dependency check - uses: gerrit0/circular-dependency-check@v2 + uses: gerrit0/circular-dependency-check@v2.0.2 with: entry: dist/index.js + build-release: + runs-on: ubuntu-latest + name: Node 22 Release + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install + run: npm ci + - name: Build + run: | + node scripts/set_strict.js false + npm run build + - name: Test + run: npm run test:full + - name: Lint + run: npm run lint -- --max-warnings 0 build-windows: runs-on: windows-latest name: Node 18 Windows steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 - name: Upgrade npm @@ -48,7 +64,5 @@ jobs: run: npm run build - name: Test run: npm run test:full - env: - SKIP_VISUAL_TEST: true - name: Lint run: npm run lint -- --max-warnings 0 diff --git a/.github/workflows/publish-beta.yml b/.github/workflows/publish-beta.yml index 4a004bf39..e9cc57dd5 100644 --- a/.github/workflows/publish-beta.yml +++ b/.github/workflows/publish-beta.yml @@ -9,22 +9,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - id: check - uses: EndBug/version-check@v1 + uses: EndBug/version-check@v2.1.4 with: diff-search: true - name: Set up Node if: steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "18" - name: Upgrade npm if: steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') run: npm i -g npm@latest - name: Install if: steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') run: npm ci + - name: Test + if: steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') + run: npm test - name: Setup publish token if: steps.check.outputs.changed == 'true' && contains(steps.check.outputs.version, 'beta') run: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc diff --git a/.github/workflows/publish-lts.yml b/.github/workflows/publish-lts.yml index ccc9e894f..2b2f84254 100644 --- a/.github/workflows/publish-lts.yml +++ b/.github/workflows/publish-lts.yml @@ -9,22 +9,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - id: check - uses: EndBug/version-check@v1 + uses: EndBug/version-check@v2.1.4 with: diff-search: true - name: Set up Node if: steps.check.outputs.changed == 'true' - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "18" - name: Upgrade npm if: steps.check.outputs.changed == 'true' run: npm i -g npm@latest - name: Install if: steps.check.outputs.changed == 'true' run: npm ci + - name: Test + if: steps.check.outputs.changed == 'true' + run: npm test - name: Setup publish token if: steps.check.outputs.changed == 'true' run: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 627066b5a..b364e0dbf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,21 +9,24 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - id: check - uses: EndBug/version-check@v2.1.0 + uses: EndBug/version-check@v2.1.4 with: diff-search: true - name: Set up Node if: steps.check.outputs.changed == 'true' - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "18" - name: Install if: steps.check.outputs.changed == 'true' run: npm ci + - name: Test + if: steps.check.outputs.changed == 'true' + run: npm test - name: Setup publish token if: steps.check.outputs.changed == 'true' run: echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml deleted file mode 100644 index 734faeee5..000000000 --- a/.github/workflows/visual-regression.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Visual Regression Testing -on: [workflow_dispatch] -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 20 - - name: Upgrade npm - run: npm i -g npm@latest - - name: Get baseline screenshots - run: | - git fetch - git checkout origin/$GITHUB_BASE_REF - npm ci - npm run build - node dist/test/capture-screenshots.js - npm run test:visual:accept - - - name: Get current screenshots - run: | - git checkout $GITHUB_SHA - npm ci - npm run build - node dist/test/capture-screenshots.js - - name: Test - run: npx reg-suit run -c .config/regconfig.json --test - - name: Upload Results - uses: actions/upload-artifact@v3 - with: - name: visual-regression - path: dist/tmp/.reg diff --git a/.npmrc b/.npmrc index 0683edb0a..b241adc44 100644 --- a/.npmrc +++ b/.npmrc @@ -3,3 +3,6 @@ # an install, so just turn it off. We can still check for findings # with npm audit --production. audit = false + +# While we're on the TS beta, need this flag. +legacy-peer-deps = true diff --git a/.vscode/settings.json b/.vscode/settings.json index 6fd759eb7..bb54bcf11 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "mochaExplorer.configFile": ".config/mocha.test-explorer.json", "cSpell.words": [ "cname", + "Combinatorially", "deserializers", "githubprivate", "linkcode", diff --git a/CHANGELOG.md b/CHANGELOG.md index baf867a4f..8ccd5abf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,157 @@ # Unreleased +## v0.26.3 (2024-06-28) + +### Features + +- "On This Page" navigation now includes the page groups in collapsible sections, #2616. + +### Bug Fixes + +- `mailto:` links are no longer incorrectly recognized as relative paths, #2613. +- Added `@since` to the default list of recognized tags, #2614. +- Relative paths to directories will no longer cause the directory to be copied into the media directory, #2617. + +## v0.26.2 (2024-06-24) + +### Features + +- Added a `--suppressCommentWarningsInDeclarationFiles` option to disable warnings from + parsing comments in declaration files, #2611. +- Improved comment discovery to more closely match TypeScript's discovery when getting comments + for members of interfaces/classes, #2084, #2545. + +### Bug Fixes + +- The `text` non-highlighted language no longer causes warnings when rendering, #2610. +- If a comment on a method is inherited from a parent class, and the child class does not + use an `@param` tag from the parent, TypeDoc will no longer warn about the `@param` tag. + +## v0.26.1 (2024-06-22) + +### Features + +- Improved Korean translation coverage, #2602. + +### Bug Fixes + +- Added `@author` to the default list of recognized tags, #2603. +- Anchor links are no longer incorrectly checked for relative paths, #2604. +- Fixed an issue where line numbers reported in error messages could be incorrect, #2605. +- Fixed relative link detection for markdown links containing code in their label, #2606. +- Fixed an issue with packages mode where TypeDoc would use (much) more memory than required, #2607. +- TypeDoc will no longer crash when asked to render highlighted code for an unsupported language, #2609. +- Fixed an issue where relatively-linked files would not be copied to the output directory in packages mode. +- Fixed an issue where modifier tags were not applied to top level modules in packages mode. +- Fixed an issue where excluded tags were not removed from top level modules in packages mode. +- `.jsonc` configuration files are now properly read as JSONC, rather than being passed to `require`. + +### Thanks! + +- @KNU-K + +# v0.26.0 (2024-06-22) + +### Breaking Changes + +- Drop support for Node 16. +- Moved from `marked` to `markdown-it` for parsing as marked has moved to an async model which supporting would significantly complicate TypeDoc's rendering code. + This means that any projects setting `markedOptions` needs to be updated to use `markdownItOptions`. + Unlike `marked@4`, `markdown-it` pushes lots of functionality to plugins. To use plugins, a JavaScript config file must be used with the `markdownItLoader` option. +- Updated Shiki from 0.14 to 1.x. This should mostly be a transparent update which adds another 23 supported languages and 13 supported themes. + As Shiki adds additional languages, the time it takes to load the highlighter increases linearly. To avoid rendering taking longer than necessary, + TypeDoc now only loads a few common languages. Additional languages can be loaded by setting the `--highlightLanguages` option. +- Changed default of `--excludePrivate` to `true`. +- Renamed `--sitemapBaseUrl` to `--hostedBaseUrl` to reflect that it can be used for more than just the sitemap. +- Removed deprecated `navigation.fullTree` option. +- Removed `--media` option, TypeDoc will now detect image links within your comments and markdown documents and automatically copy them to the site. +- Removed `--includes` option, use the `@document` tag instead. +- Removed `--stripYamlFrontmatter` option, TypeDoc will always do this now. +- Renamed the `--htmlLang` option to `--lang`. +- Removed the `--gaId` option for Google Analytics integration and corresponding `analytics` theme member, #2600. +- All function-likes may now have comments directly attached to them. This is a change from previous versions of TypeDoc where functions comments + were always moved down to the signature level. This mostly worked, but caused problems with type aliases, so was partially changed in 0.25.13. + This change was extended to apply not only to type aliases, but also other function-likes declared with variables and callable properties. + As a part of this change, comments on the implementation signature of overloaded functions will now be added to the function reflection, and will + not be inherited by signatures of that function, #2521. +- API: TypeDoc now uses a typed event emitter to provide improved type safety, this found a bug where `Converter.EVENT_CREATE_DECLARATION` + was emitted for `ProjectReflection` in some circumstances. +- API: `MapOptionDeclaration.mapError` has been removed. +- API: Deprecated `BindOption` decorator has been removed. +- API: `DeclarationReflection.indexSignature` has been renamed to `DeclarationReflection.indexSignatures`. + Note: This also affects JSON serialization. TypeDoc will support JSON output from 0.25 through at least 0.26. +- API: `JSONOutput.SignatureReflection.typeParameter` has been renamed to `typeParameters` to match the JS API. +- API: `DefaultThemeRenderContext.iconsCache` has been removed as it is no longer needed. +- API: `DefaultThemeRenderContext.hook` must now be passed `context` if required by the hook. + +### Features + +- Added support for TypeScript 5.5. +- Added new `--projectDocuments` option to specify additional Markdown documents to be included in the generated site #247, #1870, #2288, #2565. +- TypeDoc now has the architecture in place to support localization. No languages besides English + are currently shipped in the package, but it is now possible to add support for additional languages, #2475. +- Added support for a `packageOptions` object which specifies options that should be applied to each entry point when running with `--entryPointStrategy packages`, #2523. +- `--hostedBaseUrl` will now be used to generate a `` element in the project root page, #2550. +- Added support for documenting individual elements of a union type, #2585. + Note: This feature is only available on type aliases directly containing unions. +- TypeDoc will now log the number of errors/warnings errors encountered, if any, after a run, #2581. +- New option, `--customFooterHtml` to add custom HTML to the generated page footer, #2559. +- TypeDoc will now copy modifier tags to children if specified in the `--cascadedModifierTags` option, #2056. +- TypeDoc will now warn if mutually exclusive modifier tags are specified for a comment (e.g. both `@alpha` and `@beta`), #2056. +- Groups and categories can now be collapsed in the page body, #2330. +- Added support for JSDoc `@hideconstructor` tag. + This tag should only be used to work around TypeScript#58653, prefer the more general `@hidden`/`@ignore` tag to hide members normally, #2577. +- Added `--useHostedBaseUrlForAbsoluteLinks` option to use the `--hostedBaseUrl` option to produce absolute links to pages on a site, #940. +- Tag headers now generate permalinks in the default theme, #2308. +- TypeDoc now attempts to use the "most likely name" for a symbol if the symbol is not present in the documentation, #2574. +- Fixed an issue where the "On This Page" section would include markdown if the page contained headings which contained markdown. +- TypeDoc will now warn if a block tag is used which is not defined by the `--blockTags` option. +- Added three new sort strategies `documents-first`, `documents-last`, and `alphabetical-ignoring-documents` to order markdown documents. +- Added new `--alwaysCreateEntryPointModule` option. When set, TypeDoc will always create a `Module` for entry points, even if only one is provided. + If `--projectDocuments` is used to add documents, this option defaults to `true`, otherwise, defaults to `false`. +- Added new `--highlightLanguages` option to control what Shiki language packages are loaded. +- TypeDoc will now render union elements on new lines if there are more than 3 items in the union. +- TypeDoc will now only render the "Type Declaration" section if it will provide additional information not already presented in the page. + This results in significantly smaller documentation pages in many cases where that section would just repeat what has already been presented in the rendered type. +- Added `comment.beforeTags` and `comment.afterTags` hooks for plugin use. + Combined with `CommentTag.skipRendering` this can be used to provide custom tag handling at render time. + +### Bug Fixes + +- TypeDoc now supports objects with multiple index signatures, #2470. +- Header anchor links in rendered markdown are now more consistent with headers generated by TypeDoc, #2546. +- Types rendered in the `Returns` header are now properly colored, #2546. +- Links added with the `navigationLinks` option are now moved into the pull out navigation on mobile displays, #2548. +- `@license` and `@import` comments will be ignored at the top of files, #2552. +- Fixed issue in documentation validation where constructor signatures where improperly considered not documented, #2553. +- Keyboard focus is now visible on dropdowns and checkboxes in the default theme, #2556. +- The color theme label in the default theme now has an accessible name, #2557. +- Fixed issue where search results could not be navigated while Windows Narrator was on, #2563. +- `charset` is now correctly cased in `` tag generated by the default theme, #2568. +- Fixed very slow conversion on Windows where Msys git was used by typedoc to discover repository links, #2586. +- Validation will now be run in watch mode, #2584. +- Fixed an issue where custom themes which added dependencies in the `` element could result in broken icons, #2589. +- `@default` and `@defaultValue` blocks are now recognized as regular blocks if they include inline tags, #2601. +- Navigation folders sharing a name will no longer be saved with a shared key to `localStorage`. +- The `--hideParameterTypesInTitle` option no longer applies when rendering function types. +- Broken `@link` tags in readme files will now cause a warning when link validation is enabled. +- Fixed `externalSymbolLinkMappings` option's support for [meanings](https://typedoc.org/guides/declaration-references/#meaning) in declaration references. +- Buttons to copy code now have the `type=button` attribute set to avoid being treated as submit buttons. +- `--hostedBaseUrl` will now implicitly add a trailing slash to the generated URL. + +### Thanks! + +- @Aryakoste +- @bladerunner2020 +- @Dinnerbone +- @HarelM +- @kraenhansen +- @Nil2000 +- @steve02081504 +- @tristanzander + +# Unreleased + ## v0.25.13 (2024-04-07) ### Features diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..ed0341e0d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,135 @@ +// @ts-check +import eslint from "@eslint/js"; +import tslint from "typescript-eslint"; + +/** @type {import("typescript-eslint").ConfigWithExtends} */ +const config = { + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + fixStyle: "inline-type-imports", + prefer: "type-imports", + disallowTypeAnnotations: false, + }, + ], + + "@typescript-eslint/restrict-template-expressions": [ + "error", + { + allowBoolean: true, + allowNumber: true, + }, + ], + + // This one is just annoying since it complains at incomplete code + "no-empty": "off", + + // Doesn't properly handle intersections of generics. + "@typescript-eslint/unified-signatures": "off", + + // This rule is factually incorrect. Interfaces which extend some type alias can be used to introduce + // new type names. This is useful particularly when dealing with mixins. + "@typescript-eslint/no-empty-interface": "off", + + // Conflicts with TS option to require dynamic access for records, which I find more useful. + "@typescript-eslint/no-dynamic-delete": "off", + + // Conflicts with the `NeverIfInternal` type used to enforce a stricter API internally + "@typescript-eslint/no-redundant-type-constituents": "off", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", + + // This is sometimes useful for clarity + "@typescript-eslint/no-unnecessary-type-arguments": "off", + + // We still use `any` fairly frequently... + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-call": "off", + + // Really annoying, doesn't provide any value. + "@typescript-eslint/no-empty-function": "off", + + // Declaration merging with a namespace is a necessary tool when working with enums. + "@typescript-eslint/no-namespace": "off", + + // Reported by TypeScript + "@typescript-eslint/no-unused-vars": "off", + + "no-console": "warn", + + "@typescript-eslint/no-confusing-void-expression": "off", + "@typescript-eslint/unbound-method": "off", + + "@typescript-eslint/prefer-literal-enum-member": [ + "error", + { allowBitwiseExpressions: true }, + ], + + // I'd like to have this turned on, but haven't figured out how to tell it about + // checks that are correctly linted as unnecessary for TypeDoc's usage, but not + // for plugin permitted usage. + "@typescript-eslint/no-unnecessary-condition": "off", + + // Feel free to turn one of these back on and submit a PR! + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + + "no-restricted-syntax": [ + "warn", + { + selector: "ImportDeclaration[source.value=/.*perf$/]", + message: "Benchmark calls must be removed before committing.", + }, + { + selector: + "MemberExpression[object.name=type][property.name=symbol]", + message: + "Use type.getSymbol() instead, Type.symbol is not properly typed.", + }, + ], + }, +}; + +export default tslint.config( + eslint.configs.recommended, + ...tslint.configs.strictTypeChecked, + config, + { + ignores: [ + "eslint.config.mjs", + "src/test/renderer/specs", + "dist", + "docs", + "tmp", + "coverage", + "static/main.js", + "src/lib/output/themes/default/assets", + "**/node_modules", + "example", + "src/test/converter", + "src/test/converter2", + "src/test/module", + "src/test/packages", + "src/test/renderer/", + "src/test/slow/entry-points", + "scripts", + "bin", + + // Not long for this world + "src/test/events.test.ts", + ], + }, +); diff --git a/example/README.md b/example/README.md index be0f3cf83..f0c79be06 100644 --- a/example/README.md +++ b/example/README.md @@ -23,7 +23,7 @@ This project shows off some of TypeDoc's features: - Built-in support for various TypeScript language constructs - Markdown in doc comments -- Syntax highligting in code blocks +- Syntax highlighting in code blocks ## Index of Examples @@ -34,8 +34,9 @@ Here are some examples we wanted to highlight: ### Rendering -- Markdown showcase: {@link markdownShowcase | `markdownShowcase`} -- Syntax highlighting showcase: {@link syntaxHighlightingShowcase | `syntaxHighlightingShowcase` } +- External Markdown: [here](./src/documents/external-markdown.md) +- Markdown showcase: [here](./src/documents/markdown.md) +- Syntax highlighting showcase: [here](./src/documents/syntax-highlighting.md) ### Functions diff --git a/example/package-lock.json b/example/package-lock.json index b67af742e..7bd581806 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -1,235 +1,211 @@ { - "name": "typedoc-example", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "typedoc-example", - "version": "0.0.0", - "license": "Apache-2.0", - "dependencies": { - "@types/lodash": "^4.14.177", - "@types/react": "^17.0.37", - "@types/react-dom": "^17.0.11", - "lodash": "^4.17.21", - "react": "^17.0.2", - "react-dom": "^17.0.2" - }, - "devDependencies": { - "typescript": "^5.3.2" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.177", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", - "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "node_modules/@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - }, - "dependencies": { - "@types/lodash": { - "version": "4.14.177", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.177.tgz", - "integrity": "sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==" - }, - "@types/prop-types": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", - "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" - }, - "@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", - "integrity": "sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==", - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", - "dev": true - } + "name": "typedoc-example", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "typedoc-example", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@types/lodash": "^4.17.5", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "lodash": "^4.17.21", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "typescript": "^5.4.5" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/csstype": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", + "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + }, + "dependencies": { + "@types/lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==" + }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" + }, + "@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "requires": { + "@types/react": "*" + } + }, + "csstype": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", + "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true } + } } diff --git a/example/package.json b/example/package.json index d12880461..678503e4e 100644 --- a/example/package.json +++ b/example/package.json @@ -11,14 +11,14 @@ "typedoc": "node ../bin/typedoc" }, "dependencies": { - "@types/lodash": "^4.14.177", - "@types/react": "^17.0.37", - "@types/react-dom": "^17.0.11", + "@types/lodash": "^4.17.5", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "lodash": "^4.17.21", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "typescript": "^5.3.2" + "typescript": "^5.4.5" } } diff --git a/example/src/classes/CancellablePromise.ts b/example/src/classes/CancellablePromise.ts index 12427f9a5..822f9ae8e 100644 --- a/example/src/classes/CancellablePromise.ts +++ b/example/src/classes/CancellablePromise.ts @@ -381,7 +381,7 @@ export class CancellablePromise { * @returns a `CancellablePromise` that resolves after `ms` milliseconds. */ static delay(ms: number): CancellablePromise { - let timer: NodeJS.Timer | undefined; + let timer: ReturnType | undefined; let rejectFn: (reason?: any) => void = noop; const promise = new Promise((resolve, reject) => { diff --git a/example/src/documents/external-markdown.md b/example/src/documents/external-markdown.md new file mode 100644 index 000000000..cc8c3900a --- /dev/null +++ b/example/src/documents/external-markdown.md @@ -0,0 +1,92 @@ +--- +title: External Markdown +category: Documents +--- + +# External Markdown + +It can be convenient to write long-form guides/tutorials outside of doc comments. +To support this, TypeDoc supports including documents (like this page!) which exist +as standalone `.md` files in your repository. + +## Including Documents + +These documents can be included in your generated documentation in a few ways. + +1. With the [`@document`](https://typedoc.org/tags/document/) tag. +2. With the [projectDocuments] option. +3. As a child of another document using yaml frontmatter. + +### The `@document` Tag + +The `@document` tag can be placed in the comments for most types to add +a child to that reflection in the generated documentation. The content of +the `@document` tag should simply be a path to a markdown document to be +included in the site. As an example, the [tag which caused this file](https://github.com/TypeStrong/typedoc/blob/master/example/src/index.ts#L7) +to be included in the example site was formatted as: + +```ts +/** + * @document documents/external-markdown.md + */ +``` + +The document path is relative to the file in which the comment appears in. + +### Project Documents + +If your project has multiple entry points, the `@document` tag cannot be used +to place documents at the top level of the project as there is no comment location +associated with the project. For this use case, specify the [projectDocuments] +option. This option can be specified multiple times, or a glob may be specified +to include multiple documents. + +```jsonc +// typedoc.json +{ + "projectDocuments": ["documents/*.md"], +} +``` + +TypeDoc's default [sorting](https://typedoc.org/options/organization/#sort) options +will cause project documents to be re-ordered alphabetically. If not desired, sorting +for entry points can be disabled with the [sortEntryPoints](https://typedoc.org/options/organization/#sortentrypoints) +option. + +## Document Content + +Documents may include a yaml frontmatter section which can be used to control +some details about the document. + +```yaml +--- +title: External Markdown +group: Documents +category: Guides +children: + - ./child.md + - ./child2.md +--- +``` + +The `title` key specifies the document name, which will be used in the sidebar +navigation. The `group` and `category` keys are equivalent to the +[`@group`](https://typedoc.org/tags/group/)and [`@category`](https://typedoc.org/tags/category/) +tags and control how the document shows up in the Index section on the page +for the reflection which owns the document. The `children` key can be used to specify +additional documents which should be added underneath the current document. + +Documents may include relative links to images or other files/documents. TypeDoc +will detect links within markdown `[text](link)` formatted links, `` tags +and `` tags and automatically resolve them to other documents in the project. + +if a path cannot be resolved to a part of the documentation, TypeDoc will copy +the file found to a `media` folder in your generated documentation and update the +link to point to it, so relative links to images will still work. + +Documents may also include `{@link}` inline tags, which will be resolved as +[declaration references](https://typedoc.org/guides/declaration-references/) by +TypeDoc. + +[this page]: https://github.com/TypeStrong/typedoc/blob/master/example/src/documents/external-markdown.md +[projectDocuments]: https://typedoc.org/options/input/#projectdocuments diff --git a/example/src/documents/markdown.md b/example/src/documents/markdown.md new file mode 100644 index 000000000..2683eb988 --- /dev/null +++ b/example/src/documents/markdown.md @@ -0,0 +1,58 @@ +--- +title: Markdown Showcase +category: Documents +--- + +# Markdown Showcase + +All comments are parsed as **Markdown**. TypeDoc uses the +[markdown-it](https://github.com/markdown-it/markdown-it) markdown parser to _convert +comments to HTML_. + +TypeDoc also supports including arbitrary Markdown documents in your site. These can be top level +documents added with the `--projectDocuments` option or added with the `@document` tag. + +## Symbol References + +You can link to other classes, members or functions using an inline link tag. See the [TypeDoc +documentation](https://typedoc.org/tags/link/) for +details. + +## Code in Doc Comments + +Some inline code: `npm install --save-dev typedoc` + +A TypeScript code block: + +``` +// A fabulous variable +const x: number | string = 12 +``` + +See {@link syntaxHighlightingShowcase | `syntaxHighlightingShowcase`} for more code blocks. + +## A List + +- 🥚 ~~Eggs~~ +- 🍞 Bread +- 🧀 Swiss cheese + +## A Table + +| Package | Version | +| ------- | ------- | +| lodash | 4.17.21 | +| react | 17.0.2 | +| typedoc | 0.22.4 | + +A Random Shakespeare Quote + +--- + +> Rebellious subjects, enemies to peace, Profaners of this neighbour-stained +> steel,-- Will they not hear? What, ho! you men, you beasts, That quench the +> fire of your pernicious rage With purple fountains issuing from your veins + +## An Image + + diff --git a/example/src/documents/syntax-highlighting.md b/example/src/documents/syntax-highlighting.md new file mode 100644 index 000000000..6bb96094a --- /dev/null +++ b/example/src/documents/syntax-highlighting.md @@ -0,0 +1,64 @@ +--- +title: Syntax Highlighting +category: Documents +--- + +TypeDoc supports code blocks in Markdown and uses +[Shiki](https://shiki.matsu.io/) to provide syntax highlighting. + +TypeDoc supports all languages supported by Shiki, but does not load all of +them by default. The `highlightLanguages` option can be used to customize +which languages are loaded for highlighting. + +If no language is specified, the code block is assumed to be TypeScript: + +``` +// A fabulous variable +const x: number | string = 12 +``` + +You can specify the language at the start of your code block like this: + +````text +```rust +```` + +Use the `tsx` language to get JSX support: + +```tsx +function BasicComponent(): ReactElement { + return
Test
; +} +``` + +You might want to write code in the language your backend uses. Here's some +Python: + +```python +for i in range(30): + print(i + 1) +``` + +And some CSS: + +```css +.card { + background-color: white; + padding: 1rem; + border: 1px solid lightgray; +} +``` + +If you don't want syntax highlighting, use the `text` language: + +```text +package.json +src/ + index.ts + __tests__/ + index.test.ts +``` + +[**View the full list of supported +languages.**](https://github.com/shikijs/shiki/blob/main/docs/languages.md#all-languages) +You can also get this list by running `typedoc --help`. diff --git a/example/src/index.ts b/example/src/index.ts index 9d7d76b88..1347b8fff 100644 --- a/example/src/index.ts +++ b/example/src/index.ts @@ -3,6 +3,10 @@ * @categoryDescription Component * React Components -- This description is added with the `@categoryDescription` tag * on the entry point in src/index.ts + * + * @document documents/external-markdown.md + * @document documents/markdown.md + * @document documents/syntax-highlighting.md */ export * from "./functions"; export * from "./variables"; @@ -10,6 +14,5 @@ export * from "./types"; export * from "./classes"; export * from "./enums"; export * from "./reexports"; -export * from "./showcase"; export * from "./reactComponents"; export * from "./internals"; diff --git a/example/src/showcase.ts b/example/src/showcase.ts deleted file mode 100644 index 074fdd371..000000000 --- a/example/src/showcase.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * # Markdown Showcase - * - * All comments are parsed as **Markdown**. TypeDoc uses the - * [Marked](https://github.com/markedjs/marked) markdown parser to _convert - * comments to HTML_. - * - * ## Symbol References - * - * You can link to other classes, members or functions using an inline link tag. See the [TypeDoc - * documentation](https://typedoc.org/tags/link/) for - * details. - * - * ## Code in Doc Comments - * - * Some inline code: `npm install --save-dev typedoc` - * - * A TypeScript code block: - * - * ``` - * // A fabulous variable - * const x: number | string = 12 - * ``` - * - * See {@link syntaxHighlightingShowcase | `syntaxHighlightingShowcase`} for more code blocks. - * - * ## A List - * - * - 🥚 ~~Eggs~~ - * - 🍞 Bread - * - 🧀 Swiss cheese - * - * ## A Table - * - * | Package | Version | - * | ------- | ------- | - * | lodash | 4.17.21 | - * | react | 17.0.2 | - * | typedoc | 0.22.4 | - * - * A Random Shakespeare Quote - * -------------------------- - * - * > Rebellious subjects, enemies to peace, Profaners of this neighbour-stained - * > steel,-- Will they not hear? What, ho! you men, you beasts, That quench the - * > fire of your pernicious rage With purple fountains issuing from your veins - * - * ## An Image - * - * - * - * This requires the [media option](https://typedoc.org/guides/options/#media) - * to be set. - */ -export function markdownShowcase(): void { - // does nothing -} - -/** - * TypeDoc supports code blocks in Markdown and uses - * [Shiki](https://shiki.matsu.io/) to provide syntax highlighting. - * - * If no language is specified, the code block is assumed to be TypeScript: - * - * ``` - * // A fabulous variable - * const x: number | string = 12 - * ``` - * - * You can specify the language at the start of your code block like this: - * - * ````text - * ```rust - * ```` - * - * Use the `tsx` language to get JSX support: - * - * ```tsx - * function BasicComponent(): ReactElement { - * return
Test
- * } - * ``` - * - * You might want to write code in the language your backend uses. Here's some - * Python: - * - * ```python - * for i in range(30): - * print(i + 1) - * ``` - * - * And some CSS: - * - * ```css - * .card { - * background-color: white; - * padding: 1rem; - * border: 1px solid lightgray; - * } - * ``` - * - * If you don't want syntax highlighting, use the `text` language: - * - * ```text - * package.json - * src/ - * index.ts - * __tests__/ - * index.test.ts - * ``` - * - * [**View the full list of supported - * languages.**](https://github.com/shikijs/shiki/blob/main/docs/languages.md#all-languages) - * You can also get this list by running `typedoc --help`. - */ -export function syntaxHighlightingShowcase(): void { - // does nothing -} diff --git a/example/typedoc.json b/example/typedoc.json index d20904ad6..522386c2f 100644 --- a/example/typedoc.json +++ b/example/typedoc.json @@ -3,7 +3,6 @@ "name": "TypeDoc Example", "entryPoints": ["./src"], "sort": ["source-order"], - "media": "media", "categorizeByGroup": false, "searchCategoryBoosts": { "Component": 2, @@ -18,5 +17,17 @@ }, "sidebarLinks": { "API": "https://typedoc.org/api" + }, + "highlightLanguages": [ + "typescript", + "tsx", + "css", + "json", + "jsonc", + "python", + "yaml" + ], + "markdownItOptions": { + "html": true } } diff --git a/internal-docs/components-and-events.md b/internal-docs/components-and-events.md index c09cd6948..83f529790 100644 --- a/internal-docs/components-and-events.md +++ b/internal-docs/components-and-events.md @@ -1,3 +1,7 @@ +--- +title: Components and Events +--- + # Components and Events TypeDoc uses a `Component` and `Event`-based architecture. @@ -5,27 +9,17 @@ TypeDoc uses a `Component` and `Event`-based architecture. ## `Component` `Component`s can have child `Component`s. -Each child gets a name; a component cannot have 2x children with the same name. +Each child gets a name; a component cannot have multiple children with the same name. `Component` has methods / fields: `componentName` set by decorator metadata -`addComponent(name, ComponentClass)` responsible for instantiating the component `trigger()` -`bubble()` like trigger, but after trigger also calls on parent component. `Component` subclasses are annotated with a decorator. if not marked `internal` and if is a subclass of another component's childClass, then becomes registered as a `_defaultComponent` -Components are slowly being removed from TypeDoc. - -## `Event` - -`Event`s can be fired. - -`Event` has methods: -`isPropagationStopped` -`isDefaultPrevented` +This component-decorator hierarchy is slowly being removed from TypeDoc. ## `@Option` diff --git a/internal-docs/custom-themes.md b/internal-docs/custom-themes.md index 77249f6cb..367c76739 100644 --- a/internal-docs/custom-themes.md +++ b/internal-docs/custom-themes.md @@ -1,3 +1,7 @@ +--- +title: Custom Themes +--- + # Custom Themes TypeDoc 0.22 changed how themes are defined, necessarily breaking compatibility with all Handlebars based themes @@ -13,36 +17,28 @@ export function load(app: Application) { } ``` -This isn't very interesting since it exactly duplicates the default theme. Most themes need to adjust the templates -in some way. This can be done by providing them class which returns a different context class. Say we wanted to replace -TypeDoc's default analytics helper with one that uses [Open Web Analytics](https://www.openwebanalytics.com/) instead of -Google Analytics. This could be done with the following theme: +This isn't very interesting since it exactly duplicates the default theme. +Most themes need to adjust the templates in some way. This can be done by +providing them class which returns a different context class. Say we wanted +to replace TypeDoc's default footer with one that mentioned your copyright. +This could be done with the following theme. + +In this case, it would probably be better to add this content using a render +hook for `footer.begin` or `footer.end`, but it can be done in this way as well. ```tsx import { Application, DefaultTheme, PageEvent, JSX, Reflection } from "typedoc"; -const script = ` -(function() { - var _owa = document.createElement('script'); _owa.type = 'text/javascript'; - _owa.async = true; _owa.src = '${site}' + '/modules/base/js/owa.tracker-combined-min.js'; - var _owa_s = document.getElementsByTagName('script')[0]; _owa_s.parentNode.insertBefore(_owa, - _owa_s); -}()); -`.trim(); - class MyThemeContext extends DefaultThemeRenderContext { - // Important: If you use `this`, this function MUST be bound! Template functions are free - // to destructure the context object to only grab what they care about. - override analytics = () => { - // Reusing existing option rather than declaring our own for brevity - if (!this.options.isSet("gaId")) return; - - const site = this.options.getValue("gaId"); - + // Important: If you use `this`, this function MUST be bound! Template functions + // are free to destructure the context object to only grab what they care about. + override footer = (context) => { return ( - +
+ {context.hook("footer.begin", context)} + Copyright 2024 + {context.hook("footer.end", context)} +
); }; } @@ -58,7 +54,7 @@ export function load(app: Application) { } ``` -## Hooks (v0.22.8+) +## Hooks When rendering themes, TypeDoc's default theme will call several functions to allow plugins to inject HTML into a page without completely overwriting a theme. Hooks live on the parent `Renderer` and may be called diff --git a/internal-docs/internationalization.md b/internal-docs/internationalization.md new file mode 100644 index 000000000..911da19a9 --- /dev/null +++ b/internal-docs/internationalization.md @@ -0,0 +1,55 @@ +--- +title: "Adding Locales" +group: Guides +--- + +# Internationalization + +TypeDoc 0.26 added support for internationalization in TypeDoc's output. +This is controlled by the `--lang` option and will affect both console output +and the generated HTML or JSON output. + +## Adding a Locale + +Locales are stored in TypeDoc's `src/lib/internationalization/locales` directory +with the exception of the default locale, which is stored in +`src/lib/internationalization/translatable.ts`. To add a new locale, create a file +under the `locales` directory which looks like this: + +```ts +// zh.cts +import { buildTranslation } from "../translatable"; + +export = buildTranslation({ + docs_generated_at_0: "文档生成于 {0}", +}); +``` + +This will give a compiler error on `buildTranslation` since the translation object +does not provide a translation for every string supported by TypeDoc. Submitting an +incomplete translation is still greatly appreciated! If the translation is not complete +when submitting for review, import and call `buildIncompleteTranslation` instead. + +Any strings which are not added to the translation object will automatically fall back +to the default English string. + +The [TranslatableStrings](https://typedoc.org/api/interfaces/TranslatableStrings.html) +interface has documentation on the format of TypeDoc's builtin translations. In short, +translation keys include numbers to indicate placeholders in the English string, and +the translated strings should include `{n}` where the placeholder will be filled in at +runtime. + +> Please do not submit machine generated translations for languages you are unfamiliar with. +> TypeDoc relies on contributors to ensure the accuracy of included translations. + +## Validation + +The `buildTranslation` and `buildIncompleteTranslation` functions will attempt to +validate that the provided translation strings include the same number of +placeholders as the default locale. This can check that a string does not miss a +placeholder, but will not catch usage of placeholders which will not be defined by +TypeDoc. That issue will automatically be caught by a unit test if it occurs. + +The builder functions will also validate that translations do not provide keys +which are not present in the default locale if a fresh object is provided directly +to them as suggested in the example above. diff --git a/internal-docs/plugins.md b/internal-docs/plugins.md index 61752f7e4..57110eca2 100644 --- a/internal-docs/plugins.md +++ b/internal-docs/plugins.md @@ -1,3 +1,9 @@ +--- +title: Plugins +children: + - ./components-and-events.md +--- + # Writing a TypeDoc Plugin TypeDoc supports plugins which can modify how projects are converted, how converted symbols diff --git a/internal-docs/third-party-symbols.md b/internal-docs/third-party-symbols.md index c593685f8..bf7a1caee 100644 --- a/internal-docs/third-party-symbols.md +++ b/internal-docs/third-party-symbols.md @@ -23,13 +23,13 @@ detected as belonging to the `typescript` package rather than the `global` packa // For these you should probably install typedoc-plugin-mdn-links instead "global": { // Handle {@link !Promise} - "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" + "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", }, "typescript": { // Handle type X = Promise - "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" - } - } + "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", + }, + }, } ``` @@ -41,9 +41,9 @@ A wildcard can be used to provide a fallback link to any unmapped type. "externalSymbolLinkMappings": { "external-lib": { "SomeObject": "https://external-lib.site/docs/SomeObject", - "*": "https://external-lib.site/docs" - } - } + "*": "https://external-lib.site/docs", + }, + }, } ``` diff --git a/internal-docs/visual-regression-tests.md b/internal-docs/visual-regression-tests.md index 7015e2dac..ebdbd241b 100644 --- a/internal-docs/visual-regression-tests.md +++ b/internal-docs/visual-regression-tests.md @@ -1,5 +1,12 @@ # Running the Visual Regression Tests -1. Build screenshots: `npm run test:visual`. All items should be considered new. -2. Accept the screenshots: `npm run test:visual:accept` -3. Run `npm run test:visual` again. All items should pass. +When making changes to the themes, it is useful to be able to compare screenshots +from the new/old runs. + +1. Build some documentation project to `./docs` +2. Run `node scripts/capture_screenshots.mjs` +3. Run `node scripts/accept_visual_regression.js` +4. Make the UI change +5. Build the same documentation project to `./docs` +6. Run `./scripts/compare_screenshots.sh` +7. Open `./tmp/output/index.html` in a browser. diff --git a/package-lock.json b/package-lock.json index 998be1249..769a7e17e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,53 +1,162 @@ { "name": "typedoc", - "version": "0.25.13", + "version": "0.26.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typedoc", - "version": "0.25.13", + "version": "0.26.3", "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.7" + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.9.1", + "yaml": "^2.4.5" }, "bin": { "typedoc": "bin/typedoc" }, "devDependencies": { "@types/lunr": "^2.3.7", - "@types/marked": "^4.0.8", - "@types/mocha": "^10.0.6", - "@types/node": "16", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", - "@typestrong/fs-fixture-builder": "github:TypeStrong/fs-fixture-builder#8abd1494280116ff5318dde2c50ad01e1663790c", - "c8": "^9.1.0", - "esbuild": "^0.20.2", - "eslint": "^8.57.0", - "mocha": "^10.4.0", - "prettier": "3.0.3", - "puppeteer": "^13.5.2", + "@types/markdown-it": "^14.1.1", + "@types/mocha": "^10.0.7", + "@types/node": "18", + "@typestrong/fs-fixture-builder": "github:TypeStrong/fs-fixture-builder#34113409e3a171e68ce5e2b55461ef5c35591cfe", + "c8": "^10.1.2", + "esbuild": "^0.21.5", + "eslint": "^9.5.0", + "mocha": "^10.5.2", + "prettier": "3.3.2", + "puppeteer": "^22.12.1", "ts-node": "^10.9.2", - "typescript": "5.4.3" + "typescript": "5.5.2", + "typescript-eslint": "^7.14.1" }, "engines": { - "node": ">= 16" + "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, "node_modules/@bcoe/v8-coverage": { @@ -69,13 +178,14 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -85,13 +195,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -101,13 +212,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -117,13 +229,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -133,13 +246,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -149,13 +263,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -165,13 +280,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -181,13 +297,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -197,13 +314,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -213,13 +331,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -229,13 +348,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -245,13 +365,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -261,13 +382,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -277,13 +399,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -293,13 +416,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -309,13 +433,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -325,13 +450,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -341,13 +467,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -357,13 +484,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -373,13 +501,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -389,13 +518,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -405,13 +535,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -421,13 +552,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -460,44 +592,38 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.16.0.tgz", + "integrity": "sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -505,44 +631,47 @@ "node": "*" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -550,6 +679,26 @@ "node": "*" } }, + "node_modules/@eslint/js": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.5.0.tgz", + "integrity": "sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -563,17 +712,129 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -638,6 +899,53 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@shikijs/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.9.1.tgz", + "integrity": "sha512-EmUful2MQtY8KgCF1OkBtOuMcvaZEvmdubhW0UHCGXi21O9dRLeADVCj+k6ZS+de7Mz9d2qixOXJ+GLhcK3pXg==", + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -668,11 +976,12 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/lunr": { "version": "2.3.7", @@ -680,57 +989,68 @@ "integrity": "sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==", "dev": true }, - "node_modules/@types/marked": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz", - "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", - "dev": true + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/mocha": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", - "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", - "dev": true + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", + "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "16.18.87", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.87.tgz", - "integrity": "sha512-+IzfhNirR/MDbXz6Om5eHV54D9mQlEMGag6AgEzlju0xH3M8baCXYwqQ6RKgGMpn9wSTx6Ltya/0y4Z8eSfdLw==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "version": "18.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", + "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", - "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/type-utils": "7.4.0", - "@typescript-eslint/utils": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -750,15 +1070,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", - "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4" }, "engines": { @@ -778,13 +1099,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", - "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0" + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -795,15 +1117,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", - "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -822,10 +1145,11 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", - "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || >=20.0.0" }, @@ -835,19 +1159,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", - "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -863,18 +1188,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", - "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -888,13 +1211,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", - "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.4.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.14.1", + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -906,20 +1230,14 @@ }, "node_modules/@typestrong/fs-fixture-builder": { "version": "0.0.0", - "resolved": "git+ssh://git@github.com/TypeStrong/fs-fixture-builder.git#8abd1494280116ff5318dde2c50ad01e1663790c", - "integrity": "sha512-DS1emSwvN8RjElPNzV00/qkICp2R/fuiEkraaFNVTFTXcJXLqQ1KESTJXxSbSFE8AUsVgxa/XHG51pfSM0i0kw==", + "resolved": "git+ssh://git@github.com/TypeStrong/fs-fixture-builder.git#34113409e3a171e68ce5e2b55461ef5c35591cfe", + "integrity": "sha512-CfD3fWF5hYyTawCF+I3V7acIxk96M+TIwVFFotGC+K2J9nbXCrD5xmQyE2dBDEIn1VjQjKf8pc/L34ubDmcfhA==", "dev": true, "license": "MIT", "bin": { - "capture-fs-fixture": "dist/capture-fixture.ts" + "capture-fs-fixture": "dist/capture-fixture.js" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -937,6 +1255,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -951,15 +1270,16 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -967,6 +1287,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -996,11 +1317,6 @@ "node": ">=8" } }, - "node_modules/ansi-sequence-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", - "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==" - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1038,23 +1354,94 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1073,7 +1460,18 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -1084,17 +1482,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1104,12 +1491,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1140,6 +1528,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1150,15 +1539,17 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } }, "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", "dev": true, + "license": "ISC", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@istanbuljs/schema": "^0.1.3", @@ -1167,7 +1558,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", + "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -1176,7 +1567,15 @@ "c8": "bin/c8.js" }, "engines": { - "node": ">=14.14.0" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, "node_modules/callsites": { @@ -1184,6 +1583,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1255,11 +1655,20 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "node_modules/chromium-bidi": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.24.tgz", + "integrity": "sha512-5xQNN2SVBdZv4TxeMLaI+PelrnZsHDhn8h2JtyriLr+0qHcZS8BMuo93qN6J1VmtmrgYP+rmcLHcbpnA8QJh+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } }, "node_modules/cliui": { "version": "8.0.1", @@ -1305,21 +1714,39 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1334,6 +1761,16 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1369,11 +1806,27 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/diff": { "version": "5.0.0", @@ -1389,6 +1842,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -1396,17 +1850,12 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -1419,16 +1868,49 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -1436,29 +1918,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -1482,42 +1964,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.5.0.tgz", + "integrity": "sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/config-array": "^0.16.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.5.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -1531,23 +2032,24 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1575,6 +2077,19 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1588,22 +2103,50 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.11.3", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -1621,6 +2164,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -1642,6 +2186,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -1651,6 +2196,7 @@ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -1670,13 +2216,22 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1693,6 +2248,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -1704,7 +2260,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -1726,27 +2283,30 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1780,24 +2340,25 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/foreground-child": { "version": "3.1.1", @@ -1815,31 +2376,26 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "dev": true }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -1855,6 +2411,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -1865,6 +2422,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", @@ -1909,15 +2482,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1928,6 +2499,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -1943,11 +2515,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -1973,17 +2553,32 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/ieee754": { @@ -2004,7 +2599,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.1", @@ -2020,6 +2616,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2056,6 +2653,27 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2103,6 +2721,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2179,6 +2798,32 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2191,17 +2836,33 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true, + "license": "MIT" + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -2209,16 +2870,25 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -2236,6 +2906,21 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2311,33 +2996,45 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "bin": { - "marked": "bin/marked.js" + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, - "engines": { - "node": ">= 12" + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2345,9 +3042,10 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2358,21 +3056,33 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" }, "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.5.2.tgz", + "integrity": "sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.3", + "chokidar": "^3.5.3", "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", @@ -2482,24 +3192,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 0.4.0" } }, "node_modules/normalize-path": { @@ -2521,17 +3221,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -2567,13 +3267,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, "engines": { - "node": ">=6" + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" } }, "node_modules/parent-module": { @@ -2581,6 +3306,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -2588,22 +3314,32 @@ "node": ">=6" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-key": { @@ -2615,11 +3351,39 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2628,7 +3392,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2642,70 +3414,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2716,10 +3424,11 @@ } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -2735,21 +3444,54 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -2760,33 +3502,72 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "engines": { "node": ">=6" } }, "node_modules/puppeteer": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.7.0.tgz", - "integrity": "sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==", - "deprecated": "< 21.5.0 is no longer supported", + "version": "22.12.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.12.1.tgz", + "integrity": "sha512-1GxY8dnEnHr1SLzdSDr0FCjM6JQfAh2E2I/EqzeF8a58DbGVk9oVjj4lFdqNoVbpgFSpAbz7VER9St7S1wDpNg==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.12.1" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "22.12.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.12.1.tgz", + "integrity": "sha512-XmqeDPVdC5/3nGJys1jbgeoZ02wP0WV1GBlPtr/ULRbGXJFuqgXMcKQ3eeNtFpBzGRbpeoCGWHge1ZWKWl0Exw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.5.24", + "debug": "^4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" }, "engines": { - "node": ">=10.18.1" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/queue-microtask": { @@ -2809,6 +3590,13 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true, + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2818,20 +3606,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2858,6 +3632,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2872,63 +3647,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3018,14 +3736,12 @@ } }, "node_modules/shiki": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", - "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.9.1.tgz", + "integrity": "sha512-8PDkgb5ja3nfujTjvC4VytL6wGOGCtFAClUb2r3QROevYXxcq+/shVJK5s6gy0HZnjaJgFxd6BpPqpRfqne5rA==", + "license": "MIT", "dependencies": { - "ansi-sequence-parser": "^1.1.0", - "jsonc-parser": "^3.2.0", - "vscode-oniguruma": "^1.7.0", - "vscode-textmate": "^8.0.0" + "@shikijs/core": "1.9.1" } }, "node_modules/signal-exit": { @@ -3045,17 +3761,83 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string-width": { @@ -3072,6 +3854,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3084,6 +3882,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3109,87 +3921,78 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", "dev": true, + "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "license": "MIT", "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": ">=18" } }, "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "b4a": "^1.6.4" } }, "node_modules/text-table": { @@ -3202,13 +4005,15 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3216,17 +4021,12 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -3286,6 +4086,13 @@ "node": ">=0.3.1" } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3298,23 +4105,12 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3323,30 +4119,82 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.14.1.tgz", + "integrity": "sha512-Eo1X+Y0JgGPspcANKjeR6nIqXl4VL5ldXLc15k4m9upq+eY5fhU2IueiEZL6jmHrKH8aCfbIvM/v3IrX5Hg99w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.14.1", + "@typescript-eslint/parser": "7.14.1", + "@typescript-eslint/utils": "7.14.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true, + "license": "MIT" }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", @@ -3378,32 +4226,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/vscode-oniguruma": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" - }, - "node_modules/vscode-textmate": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", - "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3419,6 +4241,15 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", @@ -3442,6 +4273,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3449,16 +4299,17 @@ "dev": true }, "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -3484,6 +4335,18 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -3531,6 +4394,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -3556,6 +4420,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0ec52d3d6..a701065dd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typedoc", "description": "Create api documentation for TypeScript projects.", - "version": "0.25.13", + "version": "0.26.3", "homepage": "https://typedoc.org", "exports": { ".": "./dist/index.js", @@ -21,33 +21,33 @@ "url": "https://github.com/TypeStrong/TypeDoc/issues" }, "engines": { - "node": ">= 16" + "node": ">= 18" }, "dependencies": { "lunr": "^2.3.9", - "marked": "^4.3.0", - "minimatch": "^9.0.3", - "shiki": "^0.14.7" + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.9.1", + "yaml": "^2.4.5" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" }, "devDependencies": { "@types/lunr": "^2.3.7", - "@types/marked": "^4.0.8", - "@types/mocha": "^10.0.6", - "@types/node": "16", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", - "@typestrong/fs-fixture-builder": "github:TypeStrong/fs-fixture-builder#8abd1494280116ff5318dde2c50ad01e1663790c", - "c8": "^9.1.0", - "esbuild": "^0.20.2", - "eslint": "^8.57.0", - "mocha": "^10.4.0", - "prettier": "3.0.3", - "puppeteer": "^13.5.2", + "@types/markdown-it": "^14.1.1", + "@types/mocha": "^10.0.7", + "@types/node": "18", + "@typestrong/fs-fixture-builder": "github:TypeStrong/fs-fixture-builder#34113409e3a171e68ce5e2b55461ef5c35591cfe", + "c8": "^10.1.2", + "esbuild": "^0.21.5", + "eslint": "^9.5.0", + "mocha": "^10.5.2", + "prettier": "3.3.2", + "puppeteer": "^22.12.1", "ts-node": "^10.9.2", - "typescript": "5.4.3" + "typescript": "5.5.2", + "typescript-eslint": "^7.14.1" }, "files": [ "/bin", @@ -62,15 +62,13 @@ ], "scripts": { "test": "mocha --config .config/mocha.fast.json", - "test:cov": "c8 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: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", "example": "cd example && node ../bin/typedoc", - "test:full": "c8 mocha --config .config/mocha.full.json", - "test:visual": "ts-node ./src/test/capture-screenshots.ts && ./scripts/compare_screenshots.sh", - "test:visual:accept": "node scripts/accept_visual_regression.js", + "test:full": "c8 -r lcov -r text-summary mocha --config .config/mocha.full.json", "rebuild_specs": "node scripts/rebuild_specs.js", "build": "npm run build:tsc && npm run build:themes", "build:tsc": "tsc --project .", @@ -79,7 +77,8 @@ "build:prod:tsc": "tsc --project . --sourceMap false --declarationMap false", "lint": "eslint . && npm run prettier -- --check .", "prettier": "prettier --config .config/.prettierrc.json --ignore-path .config/.prettierignore", - "prepublishOnly": "node scripts/set_strict.js false && npm run build:prod && npm test", + "prepack": "node scripts/set_strict.js false && npm run build:prod", + "prepare": "node scripts/prepare.mjs", "postpublish": "node scripts/set_strict.js true" }, "keywords": [ diff --git a/scripts/build_themes.js b/scripts/build_themes.js index 3083e66ae..d1b255b72 100644 --- a/scripts/build_themes.js +++ b/scripts/build_themes.js @@ -1,5 +1,24 @@ // @ts-check const esbuild = require("esbuild"); +const fs = require("fs"); + +const watch = process.argv.slice(2).includes("--watch"); + +// It's convenient to be able to build the themes in watch mode without rebuilding the whole docs +// to test some change to the frontend JS. +/** @type {esbuild.Plugin} */ +const copyToDocsPlugin = { + name: "copyToDocs", + setup(build) { + if (watch) { + build.onEnd(() => { + if (fs.existsSync("docs/assets/main.js")) { + fs.copyFileSync("static/main.js", "docs/assets/main.js"); + } + }); + } + }, +}; async function main() { const context = await esbuild.context({ @@ -7,15 +26,13 @@ async function main() { bundle: true, minify: true, outfile: "static/main.js", - banner: { - js: '"use strict";', - }, logLevel: "info", + plugins: [copyToDocsPlugin], }); await context.rebuild(); - if (process.argv.slice(2).includes("--watch")) { + if (watch) { await context.watch(); } else { await context.dispose(); diff --git a/scripts/capture_screenshots.mjs b/scripts/capture_screenshots.mjs new file mode 100644 index 000000000..7bb5fe73c --- /dev/null +++ b/scripts/capture_screenshots.mjs @@ -0,0 +1,184 @@ +// @ts-check +import * as fs from "fs"; +import { platform } from "os"; +import { resolve, dirname, relative, join } from "path"; +import { parseArgs } from "util"; +import puppeteer from "puppeteer"; + +const viewport = { width: 1024, height: 768 }; + +class PQueue { + /** @private @type {Array<() => Promise>} */ + _queued = []; + /** @param {number} concurrency */ + constructor(concurrency) { + /** @private */ + this._concurrency = concurrency; + } + + /** @param {() => Promise} action */ + add(action) { + this._queued.push(action); + } + + /** @returns {Promise} */ + run() { + return new Promise((resolve, reject) => { + /** @type {Promise[]} */ + const queue = []; + const doReject = (err) => { + this._queued.length = 0; + queue.length = 0; + reject(err); + }; + const tick = () => { + while (queue.length < this._concurrency) { + const next = this._queued.shift(); + if (next) { + const nextPromise = Promise.resolve().then(next); + queue.push(nextPromise); + nextPromise.then(() => { + void queue.splice(queue.indexOf(nextPromise), 1); + tick(); + }, doReject); + } else { + break; + } + } + + if (queue.length === 0) { + resolve(); + } + }; + + tick(); + }); + } +} + +/** @param {string} root */ +function findHtmlPages(root) { + /** @type {string[]} */ + const result = []; + + const queue = [root]; + while (queue.length) { + const base = queue[0]; + queue.shift(); + for (const entry of fs.readdirSync(base, { withFileTypes: true })) { + if (entry.isFile()) { + if (entry.name.endsWith(".html")) { + result.push(join(base, entry.name)); + } + } else if (entry.isDirectory()) { + queue.push(join(base, entry.name)); + } + } + } + + return result; +} + +/** + * @param {string} baseDirectory + * @param {string} outputDirectory + * @param {number} jobs + * @param {boolean} headless + * @param {string} theme + */ +export async function captureScreenshots( + baseDirectory, + outputDirectory, + jobs, + headless, + theme, +) { + const browser = await puppeteer.launch({ + args: + platform() === "win32" + ? [] + : ["--no-sandbox", "--disable-setuid-sandbox"], + headless, + }); + + const queue = new PQueue(jobs); + const pages = findHtmlPages(resolve(baseDirectory)); + console.log(`Processing ${pages.length} pages with ${jobs} workers`); + for (const file of pages) { + queue.add(async () => { + const outputPath = resolve( + outputDirectory, + relative(baseDirectory, file).replace(/\.html$/, ".png"), + ); + fs.mkdirSync(dirname(outputPath), { recursive: true }); + + const context = await browser.createBrowserContext(); + const page = await context.newPage(); + await page.setViewport(viewport); + await page.goto(`file://${file}`, { + waitUntil: ["domcontentloaded"], + }); + + await page.evaluate( + `document.documentElement.dataset.theme = "${theme}"`, + ); + await page.screenshot({ + path: outputPath, + fullPage: true, + }); + + await context.close(); + console.log("Finished", relative(baseDirectory, file)); + }); + } + + await queue.run(); + + console.log("Finished!"); + await browser.close(); +} + +if (import.meta.url.endsWith(process.argv[1])) { + const args = parseArgs({ + options: { + jobs: { + short: "j", + type: "string", + default: "6", + }, + docs: { + short: "d", + type: "string", + default: "./docs", + }, + output: { + short: "o", + type: "string", + default: "./tmp/screenshots", + }, + debug: { + type: "boolean", + default: false, + }, + theme: { + type: "string", + default: "light", + }, + }, + }); + + const jobs = parseInt(args.values.jobs || ""); + const docs = args.values.docs || "./docs"; + const output = args.values.output || "./tmp/screenshots"; + + const start = Date.now(); + await fs.promises.rm(output, { recursive: true, force: true }); + await captureScreenshots( + docs, + output, + jobs, + !args.values.debug, + args.values.theme || "light", + ); + console.log(`Took ${(Date.now() - start) / 1000} seconds`); +} diff --git a/scripts/download_plugins.js b/scripts/download_plugins.js index 66600d03c..910970ae9 100644 --- a/scripts/download_plugins.js +++ b/scripts/download_plugins.js @@ -28,7 +28,15 @@ function exec(command) { async function getPlugins() { const plugins = JSON.parse(await exec("npm search --json typedocplugin")); - return plugins.filter((plugin) => Date.parse(plugin.date) > CUTOFF_MS); + const plugins2 = JSON.parse(await exec("npm search --json typedoc-plugin")); + const plugins3 = JSON.parse(await exec("npm search --json typedoc-theme")); + return [...plugins, ...plugins2, ...plugins3] + .filter((plugin) => { + return Date.parse(plugin.date) > CUTOFF_MS; + }) + .filter((plugin, index, arr) => { + return index === arr.findIndex((p) => p.name === plugin.name); + }); } function getTarballUrl(package) { diff --git a/scripts/generate_options_schema.js b/scripts/generate_options_schema.js index b43aad3e5..d3864252d 100644 --- a/scripts/generate_options_schema.js +++ b/scripts/generate_options_schema.js @@ -5,6 +5,9 @@ require("ts-node/register"); const { writeFileSync } = require("fs"); const { addTypeDocOptions } = require("../src/lib/utils/options/sources"); const { ParameterType } = require("../src"); +const { + Internationalization, +} = require("../src/lib/internationalization/internationalization"); const IGNORED_OPTIONS = new Set(["help", "version"]); @@ -18,13 +21,15 @@ const schema = { allowTrailingCommas: true, }; +const i18n = new Internationalization(null).proxy; + addTypeDocOptions({ /** @param {import("../src").DeclarationOption} option */ addDeclaration(option) { if (IGNORED_OPTIONS.has(option.name)) return; const data = { - description: option.help, + description: option.help(i18n), }; const type = option.type ?? ParameterType.String; diff --git a/scripts/prepare.mjs b/scripts/prepare.mjs new file mode 100644 index 000000000..4eaeca889 --- /dev/null +++ b/scripts/prepare.mjs @@ -0,0 +1,43 @@ +// @ts-check +// See https://github.com/cspotcode/workaround-broken-npm-prepack-behavior + +import { spawnSync } from "child_process"; +import { isAbsolute, normalize, relative } from "path"; + +const { + npm_config_local_prefix, + npm_config_cache, + npm_package_resolved, + npm_package_json, + npm_node_execpath, + npm_execpath, +} = process.env; + +if (!npm_node_execpath || !npm_execpath) { + process.exit(0); +} + +function isInstallingAsGitDepInNpm() { + if (!npm_config_cache) return false; + const normalizedNpmConfigCache = normalize(npm_config_cache); + + // Check if any of these paths are within npm's cache directory + for (const path of [ + npm_package_json, + npm_package_resolved, + npm_config_local_prefix, + ]) { + if (!path) continue; + // If local prefix is subdirectory of cache, assume we're being installed as + // a git dep + const normalized = normalize(path); + const rel = relative(normalizedNpmConfigCache, normalized); + if (!isAbsolute(rel) && !rel.startsWith("..")) return true; + } +} + +if (isInstallingAsGitDepInNpm()) { + spawnSync(npm_node_execpath, [npm_execpath, "run", "prepack"], { + stdio: "inherit", + }); +} diff --git a/scripts/rebuild_specs.js b/scripts/rebuild_specs.js index 43b7cf461..a890c83b1 100644 --- a/scripts/rebuild_specs.js +++ b/scripts/rebuild_specs.js @@ -20,6 +20,7 @@ async function getApp() { name: "typedoc", excludeExternals: true, disableSources: false, + excludePrivate: false, tsconfig: path.join(base, "tsconfig.json"), externalPattern: ["**/node_modules/**"], entryPointStrategy: td.EntryPointStrategy.Expand, @@ -105,6 +106,7 @@ function rebuildConverterTests(app, dirs) { for (const [file, before, after] of conversions) { const out = path.join(fullPath, `${file}.json`); if (fs.existsSync(out)) { + app.files = new td.ValidatingFileRegistry(); td.resetReflectionID(); before(app); const entry = getExpandedEntryPointsForPaths( diff --git a/scripts/testcase.js b/scripts/testcase.js index b531d9529..e7063d15a 100644 --- a/scripts/testcase.js +++ b/scripts/testcase.js @@ -1,5 +1,5 @@ // @ts-check -const marked = require("marked"); +const md = require("markdown-it"); const cp = require("child_process"); const { writeFile } = require("fs/promises"); @@ -37,24 +37,24 @@ function guessExtension(code) { } async function main() { - if (process.argv.length !== 3) { - console.log("Usage: node scripts/testcase.js "); + if (process.argv.length !== 3 && process.argv.length !== 4) { + console.log("Usage: node scripts/testcase.js [lang]"); process.exit(1); } const issue = process.argv[2]; const data = JSON.parse(await exec(curl.replace("ISSUE", issue))); - const lexer = new marked.Lexer({ gfm: true }); - const tokens = lexer.lex(data.body); + const parser = md(); + const tokens = parser.parse(data.body, {}); - const code = /** @type {marked.marked.Tokens.Code} */ ( + const code = tokens.find( (tok) => - tok.type === "code" && - ["ts", "tsx", "js", "jsx"].includes(tok.lang || ""), - ) || tokens.find((tok) => tok.type === "code") - ); + tok.tag === "code" && + ["ts", "tsx", "js", "jsx"].includes(tok.info || ""), + ) || tokens.find((tok) => tok.tag === "code"); + if (!code) { console.log("No codeblock found"); const file = `src/test/converter2/issues/gh${issue}.ts`; @@ -62,8 +62,9 @@ async function main() { return; } - const file = `src/test/converter2/issues/gh${issue}${guessExtension(code)}`; - await writeFile(file, code.text); + 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`); } diff --git a/src/index.ts b/src/index.ts index 1cb656319..5ddcd3e9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -export { Application } from "./lib/application"; +export { Application, type ApplicationEvents } from "./lib/application"; -export { EventDispatcher, Event } from "./lib/utils/events"; +export { EventDispatcher } from "./lib/utils/events"; export { resetReflectionID } from "./lib/models/reflections/abstract"; /** * All symbols documented under the Models namespace are also available in the root import. @@ -29,12 +29,14 @@ export { type MeaningKeyword, type ExternalResolveResult, type ExternalSymbolResolver, + type ConverterEvents, } from "./lib/converter"; export { Renderer, DefaultTheme, DefaultThemeRenderContext, + Slugger, UrlMapping, Theme, PageEvent, @@ -46,12 +48,12 @@ export type { RenderTemplate, RendererHooks, NavigationElement, + RendererEvents, } from "./lib/output"; export { ArgumentsReader, Option, - BindOption, CommentStyle, JSX, LogLevel, @@ -94,11 +96,10 @@ export type { JsDocCompatibility, } from "./lib/utils"; -export type { EventMap, EventCallback } from "./lib/utils/events"; - export { JSONOutput, Serializer, + type SerializerEvents, Deserializer, type Deserializable, type DeserializerComponent, @@ -106,5 +107,7 @@ export { SerializeEvent, } from "./lib/serialization"; +export * as Internationalization from "./lib/internationalization/index"; + import TypeScript from "typescript"; export { TypeScript }; diff --git a/src/lib/application-events.ts b/src/lib/application-events.ts index d13ceb070..c2222c92c 100644 --- a/src/lib/application-events.ts +++ b/src/lib/application-events.ts @@ -2,4 +2,4 @@ export const ApplicationEvents = { BOOTSTRAP_END: "bootstrapEnd", REVIVE: "reviveProject", VALIDATE_PROJECT: "validateProject", -}; +} as const; diff --git a/src/lib/application.ts b/src/lib/application.ts index 27dbac364..b5a323e18 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -3,21 +3,21 @@ import ts from "typescript"; import { Converter } from "./converter/index"; import { Renderer } from "./output/renderer"; -import { Deserializer, JSONOutput, Serializer } from "./serialization"; +import { Deserializer, type JSONOutput, Serializer } from "./serialization"; import type { ProjectReflection } from "./models/index"; import { Logger, ConsoleLogger, loadPlugins, writeFile, - OptionsReader, + type OptionsReader, TSConfigReader, TypeDocReader, PackageJsonReader, } from "./utils/index"; import { - AbstractComponent, + type AbstractComponent, ChildableComponent, Component, } from "./utils/component"; @@ -26,7 +26,7 @@ import type { TypeDocOptions } from "./utils/options/declaration"; import { unique } from "./utils/array"; import { ok } from "assert"; import { - DocumentationEntryPoint, + type DocumentationEntryPoint, EntryPointStrategy, getEntryPoints, getPackageDirectories, @@ -42,6 +42,12 @@ import { findTsConfigFile } from "./utils/tsconfig"; import { deriveRootDir, glob, readFile } from "./utils/fs"; import { resetReflectionID } from "./models/reflections/abstract"; import { addInferredDeclarationMapPaths } from "./models/reflections/ReflectionSymbolId"; +import { + Internationalization, + type TranslatedString, +} from "./internationalization/internationalization"; +import { loadShikiMetadata } from "./utils/highlighter"; +import { ValidatingFileRegistry, FileRegistry } from "./models/FileRegistry"; // eslint-disable-next-line @typescript-eslint/no-var-requires const packageInfo = require("../../package.json") as { @@ -57,7 +63,9 @@ const DETECTOR = Symbol(); export function createAppForTesting(): Application { // @ts-expect-error private constructor - return new Application(DETECTOR); + const app: Application = new Application(DETECTOR); + app.files = new FileRegistry(); + return app; } const DEFAULT_READERS = [ @@ -66,6 +74,12 @@ const DEFAULT_READERS = [ new TSConfigReader(), ]; +export interface ApplicationEvents { + bootstrapEnd: [Application]; + reviveProject: [ProjectReflection]; + validateProject: [ProjectReflection]; +} + /** * The default TypeDoc main application class. * @@ -78,11 +92,17 @@ const DEFAULT_READERS = [ * * Both the {@link Converter} and the {@link Renderer} emit a series of events while processing the project. * Subscribe to these Events to control the application flow or alter the output. + * + * @remarks + * + * Access to an Application instance can be retrieved with {@link Application.bootstrap} or + * {@link Application.bootstrapWithPlugins}. It can not be constructed manually. */ @Component({ name: "application", internal: true }) export class Application extends ChildableComponent< Application, - AbstractComponent + AbstractComponent, + ApplicationEvents > { /** * The converter used to create the declaration reflections. @@ -109,7 +129,24 @@ export class Application extends ChildableComponent< */ logger: Logger = new ConsoleLogger(); - options = new Options(); + /** + * Internationalization module which supports translating according to + * the `lang` option. + */ + internationalization = new Internationalization(this); + + /** + * Proxy based shortcuts for internationalization keys. + */ + i18n = this.internationalization.proxy; + + options = new Options(this.i18n); + + files: FileRegistry = new ValidatingFileRegistry(); + + /** @internal */ + @Option("lang") + accessor lang!: string; /** @internal */ @Option("skipErrorChecking") @@ -126,7 +163,7 @@ export class Application extends ChildableComponent< /** * The version number of TypeDoc. */ - static VERSION = packageInfo.version; + static readonly VERSION = packageInfo.version; /** * Emitted after plugins have been loaded and options have been read, but before they have been frozen. @@ -157,8 +194,9 @@ export class Application extends ChildableComponent< } super(null!); // We own ourselves - this.converter = this.addComponent("converter", Converter); - this.renderer = this.addComponent("renderer", Renderer); + this.converter = new Converter(this); + this.renderer = new Renderer(this); + this.logger.i18n = this.i18n; } /** @@ -168,6 +206,7 @@ export class Application extends ChildableComponent< options: Partial = {}, readers: readonly OptionsReader[] = DEFAULT_READERS, ): Promise { + await loadShikiMetadata(); const app = new Application(DETECTOR); readers.forEach((r) => app.options.addReader(r)); app.options.reset(); @@ -197,6 +236,7 @@ export class Application extends ChildableComponent< options: Partial = {}, readers: readonly OptionsReader[] = DEFAULT_READERS, ): Promise { + await loadShikiMetadata(); const app = new Application(DETECTOR); readers.forEach((r) => app.options.addReader(r)); await app._bootstrap(options); @@ -209,15 +249,46 @@ export class Application extends ChildableComponent< await this.options.read(this.logger); this.setOptions(options); this.logger.level = this.options.getValue("logLevel"); + for (const [lang, locales] of Object.entries( + this.options.getValue("locales"), + )) { + this.internationalization.addTranslations(lang, locales); + } if (hasBeenLoadedMultipleTimes()) { this.logger.warn( - `TypeDoc has been loaded multiple times. This is commonly caused by plugins which have their own installation of TypeDoc. The loaded paths are:\n\t${getLoadedPaths().join( - "\n\t", - )}`, + this.i18n.loaded_multiple_times_0( + getLoadedPaths().join("\n\t"), + ), ); } this.trigger(ApplicationEvents.BOOTSTRAP_END, this); + + if (!this.internationalization.hasTranslations(this.lang)) { + // Not internationalized as by definition we don't know what to include here. + this.logger.warn( + `Options specified "${this.lang}" as the language to use, but TypeDoc does not support it.` as TranslatedString, + ); + this.logger.info( + ("The supported languages are:\n\t" + + this.internationalization + .getSupportedLanguages() + .join("\n\t")) as TranslatedString, + ); + this.logger.info( + "You can define/override local locales with the `locales` option, or contribute them to TypeDoc!" as TranslatedString, + ); + } + + if ( + this.options.getValue("useHostedBaseUrlForAbsoluteLinks") && + !this.options.getValue("hostedBaseUrl") + ) { + this.logger.warn( + this.i18n.useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl(), + ); + this.options.setValue("useHostedBaseUrlForAbsoluteLinks", false); + } } private setOptions(options: Partial, reportErrors = true) { @@ -227,7 +298,7 @@ export class Application extends ChildableComponent< } catch (error) { ok(error instanceof Error); if (reportErrors) { - this.logger.error(error.message); + this.logger.error(error.message as TranslatedString); } } } @@ -280,9 +351,9 @@ export class Application extends ChildableComponent< ) ) { this.logger.warn( - `You are running with an unsupported TypeScript version! If TypeDoc crashes, this is why. TypeDoc supports ${supportedVersionMajorMinor.join( - ", ", - )}`, + this.i18n.unsupported_ts_version_0( + supportedVersionMajorMinor.join(", "), + ), ); } @@ -347,24 +418,20 @@ export class Application extends ChildableComponent< ) ) { this.logger.warn( - `You are running with an unsupported TypeScript version! TypeDoc supports ${supportedVersionMajorMinor.join( - ", ", - )}`, + this.i18n.unsupported_ts_version_0( + supportedVersionMajorMinor.join(", "), + ), ); } if (Object.keys(this.options.getCompilerOptions()).length === 0) { - this.logger.warn( - `No compiler options set. This likely means that TypeDoc did not find your tsconfig.json. Generated documentation will probably be empty.`, - ); + this.logger.warn(this.i18n.no_compiler_options_set()); } // Doing this is considerably more complicated, we'd need to manage an array of programs, not convert until all programs // have reported in the first time... just error out for now. I'm not convinced anyone will actually notice. if (this.options.getFileNames().length === 0) { - this.logger.error( - "The provided tsconfig file looks like a solution style tsconfig, which is not supported in watch mode.", - ); + this.logger.error(this.i18n.solution_not_supported_in_watch_mode()); return; } @@ -373,9 +440,7 @@ export class Application extends ChildableComponent< this.entryPointStrategy !== EntryPointStrategy.Resolve && this.entryPointStrategy !== EntryPointStrategy.Expand ) { - this.logger.error( - "entryPointStrategy must be set to either resolve or expand for watch mode.", - ); + this.logger.error(this.i18n.strategy_not_supported_in_watch_mode()); return; } @@ -407,7 +472,7 @@ export class Application extends ChildableComponent< ts.flattenDiagnosticMessageText( status.messageText, newLine, - ), + ) as TranslatedString, ); }, ); @@ -525,12 +590,11 @@ export class Application extends ChildableComponent< const start = Date.now(); out = Path.resolve(out); await this.renderer.render(project, out); + if (this.logger.hasErrors()) { - this.logger.error( - "Documentation could not be generated due to the errors above.", - ); + this.logger.error(this.i18n.docs_could_not_be_generated()); } else { - this.logger.info(`Documentation generated at ${nicePath(out)}`); + this.logger.info(this.i18n.docs_generated_at_0(nicePath(out))); this.logger.verbose(`HTML rendering took ${Date.now() - start}ms`); } } @@ -551,7 +615,7 @@ export class Application extends ChildableComponent< const space = this.options.getValue("pretty") ? "\t" : ""; await writeFile(out, JSON.stringify(ser, null, space)); - this.logger.info(`JSON written to ${nicePath(out)}`); + this.logger.info(this.i18n.json_written_to_0(nicePath(out))); this.logger.verbose(`JSON rendering took ${Date.now() - start}ms`); } @@ -569,9 +633,7 @@ export class Application extends ChildableComponent< private async _convertPackages(): Promise { if (!this.options.isSet("entryPoints")) { - this.logger.error( - "No entry points provided to packages mode, documentation cannot be generated.", - ); + this.logger.error(this.i18n.no_entry_points_for_packages()); return; } @@ -582,12 +644,11 @@ export class Application extends ChildableComponent< ); if (packageDirs.length === 0) { - this.logger.error( - "Failed to find any packages, ensure you have provided at least one directory as an entry point containing package.json", - ); + this.logger.error(this.i18n.failed_to_find_packages()); return; } + const origFiles = this.files; const origOptions = this.options; const projects: JSONOutput.ProjectReflection[] = []; @@ -595,7 +656,20 @@ export class Application extends ChildableComponent< // Generate a json file for each package for (const dir of packageDirs) { this.logger.verbose(`Reading project at ${nicePath(dir)}`); - const opts = origOptions.copyForPackage(dir); + let opts: Options; + try { + opts = origOptions.copyForPackage(dir); + } catch (error) { + ok(error instanceof Error); + this.logger.error(error.message as TranslatedString); + this.logger.info( + this.i18n.previous_error_occurred_when_reading_options_for_0( + nicePath(dir), + ), + ); + continue; + } + await opts.read(this.logger, dir); // Invalid links should only be reported after everything has been merged. opts.setValue("validation", { invalidLink: false }); @@ -604,9 +678,7 @@ export class Application extends ChildableComponent< EntryPointStrategy.Packages ) { this.logger.error( - `Project at ${nicePath( - dir, - )} has entryPointStrategy set to packages, but nested packages are not supported.`, + this.i18n.nested_packages_unsupported_0(nicePath(dir)), ); continue; } @@ -620,32 +692,43 @@ export class Application extends ChildableComponent< } for (const { dir, options } of projectsToConvert) { - this.logger.info(`Converting project at ${nicePath(dir)}`); + this.logger.info(this.i18n.converting_project_at_0(nicePath(dir))); this.options = options; - const project = await this.convert(); + this.files = new ValidatingFileRegistry(); + let project = await this.convert(); if (project) { this.validate(project); - projects.push( - this.serializer.projectToObject(project, process.cwd()), + const serialized = this.serializer.projectToObject( + project, + process.cwd(), ); + projects.push(serialized); } + // When debugging memory issues, it's useful to set these + // here so that a breakpoint on resetReflectionID below + // gets the memory as it ought to be with all TS objects released. + project = undefined; + this.files = undefined!; + // global.gc!(); + resetReflectionID(); } this.options = origOptions; + this.files = origFiles; - this.logger.info(`Merging converted projects`); if (projects.length !== packageDirs.length) { - this.logger.error( - "Failed to convert one or more packages, result will not be merged together.", - ); + this.logger.error(this.i18n.failed_to_convert_packages()); return; } + this.logger.info(this.i18n.merging_converted_projects()); const result = this.deserializer.reviveProjects( this.options.getValue("name") || "Documentation", projects, + process.cwd(), + this.files, ); this.trigger(ApplicationEvents.REVIVE, result); return result; @@ -655,7 +738,7 @@ export class Application extends ChildableComponent< const start = Date.now(); if (!this.options.isSet("entryPoints")) { - this.logger.error("No entry points provided to merge."); + this.logger.error(this.i18n.no_entry_points_to_merge()); return; } @@ -665,9 +748,7 @@ export class Application extends ChildableComponent< if (result.length === 0) { this.logger.warn( - `The entrypoint glob ${nicePath( - entry, - )} did not match any files.`, + this.i18n.entrypoint_did_not_match_files_0(nicePath(entry)), ); } else { this.logger.verbose( @@ -685,7 +766,7 @@ export class Application extends ChildableComponent< return JSON.parse(readFile(path)); } catch { this.logger.error( - `Failed to parse file at ${nicePath(path)} as json.`, + this.i18n.failed_to_parse_json_0(nicePath(path)), ); return null; } @@ -695,9 +776,18 @@ export class Application extends ChildableComponent< const result = this.deserializer.reviveProjects( this.options.getValue("name"), jsonProjects, + process.cwd(), + this.files, ); this.logger.verbose(`Reviving projects took ${Date.now() - start}ms`); + // If we only revived one project, the project documents were set for + // it when it was created. If we revived more than one project then + // it's convenient to be able to add more documents now. + if (jsonProjects.length > 1) { + this.converter.addProjectDocuments(result); + } + this.trigger(ApplicationEvents.REVIVE, result); return result; } diff --git a/src/lib/cli.ts b/src/lib/cli.ts index 7bdf947e8..6c519ad27 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -31,6 +31,7 @@ async function main() { const exitCode = await run(app); if (exitCode !== ExitCodes.Watching) { app.logger.verbose(`Full run took ${Date.now() - start}ms`); + logRunSummary(app.logger); process.exit(exitCode); } } catch (error) { @@ -52,7 +53,7 @@ async function run(app: td.Application) { } if (app.options.getValue("help")) { - console.log(app.options.getHelp()); + console.log(app.options.getHelp(app.i18n)); return ExitCodes.Ok; } @@ -73,6 +74,8 @@ async function run(app: td.Application) { if (app.options.getValue("watch")) { app.convertAndWatch(async (project) => { + app.validate(project); + const json = app.options.getValue("json"); if (!json || app.options.isSet("out")) { @@ -135,3 +138,25 @@ async function run(app: td.Application) { return ExitCodes.Ok; } + +/** + * Generate a string with the number of errors and warnings found. + */ +function logRunSummary(logger: td.Logger): void { + const { errorCount, warningCount } = logger; + if (errorCount) { + logger.error( + logger.i18n.found_0_errors_and_1_warnings( + errorCount.toString(), + warningCount.toString(), + ), + ); + } else if (warningCount) { + logger.warn( + logger.i18n.found_0_errors_and_1_warnings( + errorCount.toString(), + warningCount.toString(), + ), + ); + } +} diff --git a/src/lib/converter/comments/blockLexer.ts b/src/lib/converter/comments/blockLexer.ts index 39d627679..8182dbeeb 100644 --- a/src/lib/converter/comments/blockLexer.ts +++ b/src/lib/converter/comments/blockLexer.ts @@ -1,5 +1,5 @@ import ts from "typescript"; -import { Token, TokenSyntaxKind } from "./lexer"; +import { type Token, TokenSyntaxKind } from "./lexer"; import { ReflectionSymbolId } from "../../models/reflections/ReflectionSymbolId"; import { resolveAliasedSymbol } from "../utils/symbols"; diff --git a/src/lib/converter/comments/declarationReference.ts b/src/lib/converter/comments/declarationReference.ts index 9a2575a27..b1b3466d7 100644 --- a/src/lib/converter/comments/declarationReference.ts +++ b/src/lib/converter/comments/declarationReference.ts @@ -42,6 +42,19 @@ export interface Meaning { index?: number; } +export function meaningToString(meaning: Meaning): string { + let result = ""; + if (meaning.keyword) { + result += meaning.keyword; + } else if (meaning.label) { + result += meaning.label; + } + if (typeof meaning.index === "number") { + result += `(${meaning.index})`; + } + return result; +} + export interface SymbolReference { path?: ComponentPath[]; meaning?: Meaning; diff --git a/src/lib/converter/comments/declarationReferenceResolver.ts b/src/lib/converter/comments/declarationReferenceResolver.ts index e5874e004..2d8f8ad04 100644 --- a/src/lib/converter/comments/declarationReferenceResolver.ts +++ b/src/lib/converter/comments/declarationReferenceResolver.ts @@ -3,7 +3,7 @@ import { ContainerReflection, DeclarationReflection, ReferenceReflection, - Reflection, + type Reflection, ReflectionKind, } from "../../models"; import { assertNever, filterMap } from "../../utils"; @@ -38,7 +38,11 @@ export function resolveDeclarationReference( } else if (ref.resolutionStart === "global") { high.push(reflection.project); } else { - ok(ref.resolutionStart === "local"); + // Work around no-unnecessary-condition, should be unnecessary... want a trap if it ever becomes false. + ok( + ref.resolutionStart.startsWith("local") && + ref.resolutionStart.length === 5, + ); // TypeScript's behavior is to first try to resolve links via variable scope, and then // if the link still hasn't been found, check either siblings (if comment belongs to a member) // or otherwise children. @@ -65,7 +69,7 @@ export function resolveDeclarationReference( } else if (high[0] !== reflection) { if (reflection.parent instanceof ContainerReflection) { high.push( - ...(reflection.parent.children?.filter( + ...(reflection.parent.childrenIncludingDocuments?.filter( (c) => c.name === reflection.name, ) || []), ); @@ -161,7 +165,7 @@ function resolveKeyword( const ctor = (refl as ContainerReflection).children?.find((c) => c.kindOf(ReflectionKind.Constructor), ); - return (ctor as DeclarationReflection)?.signatures; + return (ctor as DeclarationReflection).signatures; } break; @@ -177,8 +181,8 @@ function resolveKeyword( return (refl as DeclarationReflection).signatures; case "index": - if ((refl as DeclarationReflection).indexSignature) { - return [(refl as DeclarationReflection).indexSignature!]; + if ((refl as DeclarationReflection).indexSignatures) { + return (refl as DeclarationReflection).indexSignatures; } break; @@ -210,7 +214,10 @@ function resolveSymbolReferencePart( let high: Reflection[] = []; let low: Reflection[] = []; - if (!(refl instanceof ContainerReflection) || !refl.children) { + if ( + !(refl instanceof ContainerReflection) || + !refl.childrenIncludingDocuments + ) { return { high, low }; } @@ -220,12 +227,12 @@ function resolveSymbolReferencePart( // so that resolution doesn't behave very poorly with projects using JSDoc style resolution. // Also is more consistent with how TypeScript resolves link tags. case ".": - high = refl.children.filter( + high = refl.childrenIncludingDocuments.filter( (r) => r.name === path.path && (r.kindOf(ReflectionKind.SomeExport) || r.flags.isStatic), ); - low = refl.children.filter( + low = refl.childrenIncludingDocuments.filter( (r) => r.name === path.path && (!r.kindOf(ReflectionKind.SomeExport) || !r.flags.isStatic), @@ -235,13 +242,14 @@ function resolveSymbolReferencePart( // Resolve via "members", interface children, class instance properties/accessors/methods, // enum members, type literal properties case "#": - high = refl.children.filter((r) => { - return ( - r.name === path.path && - r.kindOf(ReflectionKind.SomeMember) && - !r.flags.isStatic - ); - }); + high = + refl.children?.filter((r) => { + return ( + r.name === path.path && + r.kindOf(ReflectionKind.SomeMember) && + !r.flags.isStatic + ); + }) || []; break; // Resolve via "locals"... treat this as a stricter `.` which only supports traversing @@ -250,7 +258,7 @@ function resolveSymbolReferencePart( if ( refl.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project) ) { - high = refl.children.filter((r) => r.name === path.path); + high = refl.children?.filter((r) => r.name === path.path) || []; } break; } diff --git a/src/lib/converter/comments/discovery.ts b/src/lib/converter/comments/discovery.ts index 2a15f4a99..9811fb982 100644 --- a/src/lib/converter/comments/discovery.ts +++ b/src/lib/converter/comments/discovery.ts @@ -1,15 +1,17 @@ import ts from "typescript"; import { ReflectionKind } from "../../models"; -import { assertNever, Logger } from "../../utils"; +import { assertNever, type Logger } from "../../utils"; import { CommentStyle } from "../../utils/options/declaration"; import { nicePath } from "../../utils/paths"; import { ok } from "assert"; +import { filter, firstDefined } from "../../utils/array"; const variablePropertyKinds = [ ts.SyntaxKind.PropertyDeclaration, ts.SyntaxKind.PropertySignature, ts.SyntaxKind.BinaryExpression, ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.ShorthandPropertyAssignment, // class X { constructor(/** Comment */ readonly z: string) } ts.SyntaxKind.Parameter, // Variable values @@ -43,6 +45,9 @@ const wantedKinds: Record = { ts.SyntaxKind.BindingElement, ts.SyntaxKind.ExportAssignment, ts.SyntaxKind.PropertyAccessExpression, + ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.ShorthandPropertyAssignment, ], [ReflectionKind.Enum]: [ ts.SyntaxKind.EnumDeclaration, @@ -61,6 +66,9 @@ const wantedKinds: Record = { ts.SyntaxKind.VariableDeclaration, ts.SyntaxKind.ExportAssignment, ts.SyntaxKind.PropertyAccessExpression, + ts.SyntaxKind.PropertyDeclaration, + ts.SyntaxKind.PropertyAssignment, + ts.SyntaxKind.ShorthandPropertyAssignment, ], [ReflectionKind.Class]: [ ts.SyntaxKind.ClassDeclaration, @@ -102,35 +110,39 @@ const wantedKinds: Record = { ts.SyntaxKind.NamespaceExport, ts.SyntaxKind.ExportSpecifier, ], + // Non-TS kind, will never have comments. + [ReflectionKind.Document]: [], }; export interface DiscoveredComment { file: ts.SourceFile; ranges: ts.CommentRange[]; jsDoc: ts.JSDoc | undefined; + inheritedFromParentDeclaration: boolean; } -export function discoverFileComment( +export function discoverFileComments( node: ts.SourceFile, commentStyle: CommentStyle, -) { +): DiscoveredComment[] { const text = node.text; const comments = collectCommentRanges( ts.getLeadingCommentRanges(text, node.pos), ); - const selectedDocComment = comments.find((ranges) => + const selectedDocComments = comments.filter((ranges) => permittedRange(text, ranges, commentStyle), ); - if (selectedDocComment) { + return selectedDocComments.map((ranges) => { return { file: node, - ranges: selectedDocComment, - jsDoc: findJsDocForComment(node, selectedDocComment), + ranges, + jsDoc: findJsDocForComment(node, ranges), + inheritedFromParentDeclaration: false, }; - } + }); } export function discoverNodeComment( @@ -152,91 +164,142 @@ export function discoverNodeComment( file: node.getSourceFile(), ranges: selectedDocComment, jsDoc: findJsDocForComment(node, selectedDocComment), + inheritedFromParentDeclaration: false, }; } } +function checkCommentDeclarations( + commentNodes: ReadonlyArray<{ + node: ts.Node; + inheritedFromParentDeclaration: boolean; + }>, + reverse: boolean, + commentStyle: CommentStyle, +) { + const discovered: DiscoveredComment[] = []; + + for (const { node, inheritedFromParentDeclaration } of commentNodes) { + const text = node.getSourceFile().text; + + const comments = collectCommentRanges( + ts.getLeadingCommentRanges(text, node.pos), + ); + + if (reverse) { + comments.reverse(); + } + + const selectedDocComment = comments.find((ranges) => + permittedRange(text, ranges, commentStyle), + ); + + if (selectedDocComment) { + discovered.push({ + file: node.getSourceFile(), + ranges: selectedDocComment, + jsDoc: findJsDocForComment(node, selectedDocComment), + inheritedFromParentDeclaration, + }); + } + } + + return discovered; +} + export function discoverComment( symbol: ts.Symbol, kind: ReflectionKind, logger: Logger, commentStyle: CommentStyle, + checker: ts.TypeChecker, ): DiscoveredComment | undefined { // For a module comment, we want the first one defined in the file, // not the last one, since that will apply to the import or declaration. const reverse = !symbol.declarations?.some(ts.isSourceFile); - const discovered: DiscoveredComment[] = []; - const seen = new Set(); - - for (const decl of symbol.declarations || []) { - const text = decl.getSourceFile().text; - if (wantedKinds[kind].includes(decl.kind)) { - const node = declarationToCommentNode(decl); - if (!node || seen.has(node)) { - continue; - } - seen.add(node); - - // Special behavior here! We temporarily put the implementation comment - // on the reflection which contains all the signatures. This lets us pull - // the comment on the implementation if some signature does not have a comment. - // However, we don't want to skip the node if it is a reference to something. - // See the gh1770 test for an example. - if ( - kind & ReflectionKind.ContainsCallSignatures && - [ - ts.SyntaxKind.FunctionDeclaration, - ts.SyntaxKind.MethodDeclaration, - ts.SyntaxKind.Constructor, - ].includes(node.kind) && - !(node as ts.FunctionDeclaration).body - ) { - continue; - } - - const comments = collectCommentRanges( - ts.getLeadingCommentRanges(text, node.pos), - ); + const wantedDeclarations = filter(symbol.declarations, (decl) => + wantedKinds[kind].includes(decl.kind), + ); - if (reverse) { - comments.reverse(); - } + const commentNodes = wantedDeclarations.flatMap((decl) => + declarationToCommentNodes(decl, checker), + ); - const selectedDocComment = comments.find((ranges) => - permittedRange(text, ranges, commentStyle), + // Special behavior here! + // Signatures and symbols have two distinct discovery methods as of TypeDoc 0.26. + // This method discovers comments for symbols, and function-likes will only have + // a symbol comment if there is more than one signature (== more than one declaration) + // and there is a comment on the implementation signature. + if (kind & ReflectionKind.ContainsCallSignatures) { + const canHaveOverloads = wantedDeclarations.some((node) => + [ + ts.SyntaxKind.FunctionDeclaration, + ts.SyntaxKind.MethodDeclaration, + ts.SyntaxKind.Constructor, + ].includes(node.kind), + ); + + const isOverloaded = canHaveOverloads && wantedDeclarations.length > 1; + + if (isOverloaded) { + commentNodes.length = 0; + + const implementationNode = wantedDeclarations.find( + (node) => (node as ts.FunctionDeclaration).body, ); - - if (selectedDocComment) { - discovered.push({ - file: decl.getSourceFile(), - ranges: selectedDocComment, - jsDoc: findJsDocForComment(node, selectedDocComment), + if (implementationNode) { + commentNodes.push({ + node: implementationNode, + inheritedFromParentDeclaration: false, }); } + } else if (canHaveOverloads) { + // Single signature function, function reflection doesn't get a comment, + // the signatures do. + commentNodes.length = 0; + } else { + // Variable declaration which happens to include signatures. } } + const discovered = checkCommentDeclarations( + commentNodes, + reverse, + commentStyle, + ); + switch (discovered.length) { case 0: return undefined; case 1: return discovered[0]; default: { - logger.warn( - `${symbol.name} has multiple declarations with a comment. An arbitrary comment will be used.`, - ); - const locations = discovered.map(({ file, ranges: [{ pos }] }) => { - const path = nicePath(file.fileName); - const line = - ts.getLineAndCharacterOfPosition(file, pos).line + 1; - return `${path}:${line}`; - }); - logger.info( - `The comments for ${ - symbol.name - } are declared at:\n\t${locations.join("\n\t")}`, - ); + if ( + discovered.filter((n) => !n.inheritedFromParentDeclaration) + .length > 1 + ) { + logger.warn( + logger.i18n.symbol_0_has_multiple_declarations_with_comment( + symbol.name, + ), + ); + const locations = discovered.map( + ({ file, ranges: [{ pos }] }) => { + const path = nicePath(file.fileName); + const line = + ts.getLineAndCharacterOfPosition(file, pos).line + + 1; + return `${path}:${line}`; + }, + ); + logger.info( + logger.i18n.comments_for_0_are_declared_at_1( + symbol.name, + locations.join("\n\t"), + ), + ); + } return discovered[0]; } } @@ -244,46 +307,49 @@ export function discoverComment( export function discoverSignatureComment( declaration: ts.SignatureDeclaration | ts.JSDocSignature, + checker: ts.TypeChecker, commentStyle: CommentStyle, ): DiscoveredComment | undefined { - const node = declarationToCommentNode(declaration); - if (!node) { - return; - } - - if (ts.isJSDocSignature(node)) { - const comment = node.parent.parent; - ok(ts.isJSDoc(comment)); - - return { - file: node.getSourceFile(), - ranges: [ - { - kind: ts.SyntaxKind.MultiLineCommentTrivia, - pos: comment.pos, - end: comment.end, - }, - ], - jsDoc: comment, - }; - } - - const text = node.getSourceFile().text; - - const comments = collectCommentRanges( - ts.getLeadingCommentRanges(text, node.pos), - ); - comments.reverse(); + for (const { + node, + inheritedFromParentDeclaration, + } of declarationToCommentNodes(declaration, checker)) { + if (ts.isJSDocSignature(node)) { + const comment = node.parent.parent; + ok(ts.isJSDoc(comment)); + + return { + file: node.getSourceFile(), + ranges: [ + { + kind: ts.SyntaxKind.MultiLineCommentTrivia, + pos: comment.pos, + end: comment.end, + }, + ], + jsDoc: comment, + inheritedFromParentDeclaration, + }; + } - const comment = comments.find((ranges) => - permittedRange(text, ranges, commentStyle), - ); - if (comment) { - return { - file: node.getSourceFile(), - ranges: comment, - jsDoc: findJsDocForComment(node, comment), - }; + const text = node.getSourceFile().text; + + const comments = collectCommentRanges( + ts.getLeadingCommentRanges(text, node.pos), + ); + comments.reverse(); + + const comment = comments.find((ranges) => + permittedRange(text, ranges, commentStyle), + ); + if (comment) { + return { + file: node.getSourceFile(), + ranges: comment, + jsDoc: findJsDocForComment(node, comment), + inheritedFromParentDeclaration, + }; + } } } @@ -328,10 +394,7 @@ function isTopmostModuleDeclaration(node: ts.ModuleDeclaration): boolean { * ``` */ function getRootModuleDeclaration(node: ts.ModuleDeclaration): ts.Node { - while ( - node.parent && - node.parent.kind === ts.SyntaxKind.ModuleDeclaration - ) { + while (node.parent.kind === ts.SyntaxKind.ModuleDeclaration) { const parent = node.parent; if (node.name.pos === parent.name.end + 1) { node = parent; @@ -343,7 +406,10 @@ function getRootModuleDeclaration(node: ts.ModuleDeclaration): ts.Node { return node; } -function declarationToCommentNode(node: ts.Declaration): ts.Node | undefined { +function declarationToCommentNodeIgnoringParents( + node: ts.Declaration, +): ts.Node | undefined { + // ts.SourceFile is a counterexample if (!node.parent) return node; // const abc = 123 @@ -390,8 +456,80 @@ function declarationToCommentNode(node: ts.Declaration): ts.Node | undefined { if (ts.SyntaxKind.NamespaceExport === node.kind) { return node.parent; } +} - return node; +function declarationToCommentNodes( + node: ts.Declaration, + checker: ts.TypeChecker, +): ReadonlyArray<{ node: ts.Node; inheritedFromParentDeclaration: boolean }> { + const commentNode = declarationToCommentNodeIgnoringParents(node); + + if (commentNode) { + return [ + { + node: commentNode, + inheritedFromParentDeclaration: false, + }, + ]; + } + + const result: { node: ts.Node; inheritedFromParentDeclaration: boolean }[] = + [ + { + node, + inheritedFromParentDeclaration: false, + }, + ]; + + const seenSymbols = new Set(); + const bases = findBaseOfDeclaration(checker, node, (symbol) => { + if (!seenSymbols.has(symbol)) { + seenSymbols.add(symbol); + return symbol.declarations?.map( + (node) => declarationToCommentNodeIgnoringParents(node) || node, + ); + } + }); + + for (const parentCommentNode of bases || []) { + result.push({ + node: parentCommentNode, + inheritedFromParentDeclaration: true, + }); + } + + return result; +} + +// Lifted from the TS source, with a couple minor modifications +function findBaseOfDeclaration( + checker: ts.TypeChecker, + declaration: ts.Declaration, + cb: (symbol: ts.Symbol) => T[] | undefined, +): T[] | undefined { + const classOrInterfaceDeclaration = + declaration.parent?.kind === ts.SyntaxKind.Constructor + ? declaration.parent.parent + : declaration.parent; + if (!classOrInterfaceDeclaration) return; + + const isStaticMember = + ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Static; + return firstDefined( + ts.getAllSuperTypeNodes(classOrInterfaceDeclaration), + (superTypeNode) => { + const baseType = checker.getTypeAtLocation(superTypeNode); + const type = + isStaticMember && baseType.symbol + ? checker.getTypeOfSymbol(baseType.symbol) + : baseType; + const symbol = checker.getPropertyOfType( + type, + declaration.symbol!.name, + ); + return symbol ? cb(symbol) : undefined; + }, + ); } /** diff --git a/src/lib/converter/comments/index.ts b/src/lib/converter/comments/index.ts index da7b776db..391acb811 100644 --- a/src/lib/converter/comments/index.ts +++ b/src/lib/converter/comments/index.ts @@ -1,26 +1,30 @@ import ts from "typescript"; import { Comment, ReflectionKind } from "../../models"; -import { assertNever, Logger } from "../../utils"; +import { assertNever, type Logger } from "../../utils"; import type { CommentStyle, JsDocCompatibility, } from "../../utils/options/declaration"; import { lexBlockComment } from "./blockLexer"; import { - DiscoveredComment, + type DiscoveredComment, discoverComment, - discoverFileComment, + discoverFileComments, discoverNodeComment, discoverSignatureComment, } from "./discovery"; import { lexLineComments } from "./lineLexer"; import { parseComment } from "./parser"; +import type { FileRegistry } from "../../models/FileRegistry"; export interface CommentParserConfig { blockTags: Set; inlineTags: Set; modifierTags: Set; jsDocCompatibility: JsDocCompatibility; + suppressCommentWarningsInDeclarationFiles: boolean; + useTsLinkResolution: boolean; + commentStyle: CommentStyle; } const jsDocCommentKinds = [ @@ -31,6 +35,7 @@ const jsDocCommentKinds = [ ts.SyntaxKind.JSDocEnumTag, ]; +let commentDiscoveryId = 0; let commentCache = new WeakMap>(); // We need to do this for tests so that changing the tsLinkResolution option @@ -38,6 +43,7 @@ let commentCache = new WeakMap>(); // have the TS symbols attached. export function clearCommentCache() { commentCache = new WeakMap(); + commentDiscoveryId = 0; } function getCommentWithCache( @@ -45,13 +51,17 @@ function getCommentWithCache( config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker | undefined, + files: FileRegistry, ) { if (!discovered) return; const { file, ranges, jsDoc } = discovered; const cache = commentCache.get(file) || new Map(); - if (cache?.has(ranges[0].pos)) { - return cache.get(ranges[0].pos)!.clone(); + if (cache.has(ranges[0].pos)) { + const clone = cache.get(ranges[0].pos)!.clone(); + clone.inheritedFromParentDeclaration = + discovered.inheritedFromParentDeclaration; + return clone; } let comment: Comment; @@ -68,6 +78,7 @@ function getCommentWithCache( config, file, logger, + files, ); break; case ts.SyntaxKind.SingleLineCommentTrivia: @@ -76,12 +87,16 @@ function getCommentWithCache( config, file, logger, + files, ); break; default: assertNever(ranges[0].kind); } + comment.discoveryId = ++commentDiscoveryId; + comment.inheritedFromParentDeclaration = + discovered.inheritedFromParentDeclaration; cache.set(ranges[0].pos, comment); commentCache.set(file, cache); @@ -94,8 +109,19 @@ function getCommentImpl( logger: Logger, moduleComment: boolean, checker: ts.TypeChecker | undefined, + files: FileRegistry, ) { - const comment = getCommentWithCache(commentSource, config, logger, checker); + const comment = getCommentWithCache( + commentSource, + config, + logger, + config.useTsLinkResolution ? checker : undefined, + files, + ); + + if (comment?.getTag("@import") || comment?.getTag("@license")) { + return; + } if (moduleComment && comment) { // Module comment, make sure it is tagged with @packageDocumentation or @module. @@ -126,8 +152,8 @@ export function getComment( kind: ReflectionKind, config: CommentParserConfig, logger: Logger, - commentStyle: CommentStyle, - checker: ts.TypeChecker | undefined, + checker: ts.TypeChecker, + files: FileRegistry, ): Comment | undefined { const declarations = symbol.declarations || []; @@ -140,11 +166,16 @@ export function getComment( config, logger, checker, + files, ); } + const sf = declarations.find(ts.isSourceFile); + if (sf) { + return getFileComment(sf, config, logger, checker, files); + } + const isModule = declarations.some((decl) => { - if (ts.isSourceFile(decl)) return true; if (ts.isModuleDeclaration(decl) && ts.isStringLiteral(decl.name)) { return true; } @@ -152,11 +183,12 @@ export function getComment( }); const comment = getCommentImpl( - discoverComment(symbol, kind, logger, commentStyle), + discoverComment(symbol, kind, logger, config.commentStyle, checker), config, logger, isModule, checker, + files, ); if (!comment && kind === ReflectionKind.Property) { @@ -164,8 +196,8 @@ export function getComment( symbol, config, logger, - commentStyle, checker, + files, ); } @@ -174,18 +206,19 @@ export function getComment( export function getNodeComment( node: ts.Node, - kind: ReflectionKind, + moduleComment: boolean, config: CommentParserConfig, logger: Logger, - commentStyle: CommentStyle, checker: ts.TypeChecker | undefined, + files: FileRegistry, ) { return getCommentImpl( - discoverNodeComment(node, commentStyle), + discoverNodeComment(node, config.commentStyle), config, logger, - kind === ReflectionKind.Module, + moduleComment, checker, + files, ); } @@ -193,40 +226,53 @@ export function getFileComment( file: ts.SourceFile, config: CommentParserConfig, logger: Logger, - commentStyle: CommentStyle, checker: ts.TypeChecker | undefined, + files: FileRegistry, ): Comment | undefined { - return getCommentImpl( - discoverFileComment(file, commentStyle), - config, - logger, - /* moduleComment */ true, - checker, - ); + for (const commentSource of discoverFileComments( + file, + config.commentStyle, + )) { + const comment = getCommentWithCache( + commentSource, + config, + logger, + config.useTsLinkResolution ? checker : undefined, + files, + ); + + if (comment?.getTag("@license") || comment?.getTag("@import")) { + continue; + } + + if ( + comment?.getTag("@module") || + comment?.hasModifier("@packageDocumentation") + ) { + return comment; + } + return; + } } function getConstructorParamPropertyComment( symbol: ts.Symbol, config: CommentParserConfig, logger: Logger, - commentStyle: CommentStyle, - checker: ts.TypeChecker | undefined, + checker: ts.TypeChecker, + files: FileRegistry, ): Comment | undefined { const decl = symbol.declarations?.find(ts.isParameter); if (!decl) return; const ctor = decl.parent; - const comment = getSignatureComment( - ctor, - config, - logger, - commentStyle, - checker, - ); + const comment = getSignatureComment(ctor, config, logger, checker, files); const paramTag = comment?.getIdentifiedTag(symbol.name, "@param"); if (paramTag) { - return new Comment(paramTag.content); + const result = new Comment(paramTag.content); + result.sourcePath = comment!.sourcePath; + return result; } } @@ -234,15 +280,16 @@ export function getSignatureComment( declaration: ts.SignatureDeclaration | ts.JSDocSignature, config: CommentParserConfig, logger: Logger, - commentStyle: CommentStyle, - checker: ts.TypeChecker | undefined, + checker: ts.TypeChecker, + files: FileRegistry, ): Comment | undefined { return getCommentImpl( - discoverSignatureComment(declaration, commentStyle), + discoverSignatureComment(declaration, checker, config.commentStyle), config, logger, false, checker, + files, ); } @@ -256,6 +303,7 @@ export function getJsDocComment( config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker | undefined, + files: FileRegistry, ): Comment | undefined { const file = declaration.getSourceFile(); @@ -277,15 +325,19 @@ export function getJsDocComment( }, ], jsDoc: parent, + inheritedFromParentDeclaration: false, }, config, logger, - checker, + config.useTsLinkResolution ? checker : undefined, + files, )!; // And pull out the tag we actually care about. if (ts.isJSDocEnumTag(declaration)) { - return new Comment(comment.getTag("@enum")?.content); + const result = new Comment(comment.getTag("@enum")?.content); + result.sourcePath = comment.sourcePath; + return result; } if ( @@ -297,7 +349,7 @@ export function getJsDocComment( // 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( - `TypeDoc does not support multiple type parameters defined in a single @template tag with a comment.`, + logger.i18n.multiple_type_parameters_on_template_tag_unsupported(), declaration, ); return; @@ -319,10 +371,12 @@ export function getJsDocComment( if (!tag) { logger.error( - `Failed to find JSDoc tag for ${name} after parsing comment, please file a bug report.`, + logger.i18n.failed_to_find_jsdoc_tag_for_name_0(name), declaration, ); } else { - return new Comment(Comment.cloneDisplayParts(tag.content)); + const result = new Comment(Comment.cloneDisplayParts(tag.content)); + result.sourcePath = comment.sourcePath; + return result; } } diff --git a/src/lib/converter/comments/lineLexer.ts b/src/lib/converter/comments/lineLexer.ts index 1a197f962..75f3b586d 100644 --- a/src/lib/converter/comments/lineLexer.ts +++ b/src/lib/converter/comments/lineLexer.ts @@ -1,5 +1,5 @@ import type * as ts from "typescript"; -import { Token, TokenSyntaxKind } from "./lexer"; +import { type Token, TokenSyntaxKind } from "./lexer"; export function* lexLineComments( file: string, diff --git a/src/lib/converter/comments/linkResolver.ts b/src/lib/converter/comments/linkResolver.ts index ea43f65ce..135f0a158 100644 --- a/src/lib/converter/comments/linkResolver.ts +++ b/src/lib/converter/comments/linkResolver.ts @@ -1,14 +1,14 @@ import ts from "typescript"; import { - Comment, - CommentDisplayPart, + type Comment, + type CommentDisplayPart, DeclarationReflection, - InlineTagDisplayPart, - Reflection, + type InlineTagDisplayPart, + type Reflection, ReflectionSymbolId, } from "../../models"; import { - DeclarationReference, + type DeclarationReference, parseDeclarationReference, } from "./declarationReference"; import { resolveDeclarationReference } from "./declarationReferenceResolver"; @@ -65,6 +65,15 @@ export function resolveLinks( options, ); } + + if (reflection.isDocument()) { + reflection.content = resolvePartLinks( + reflection, + reflection.content, + externalResolver, + options, + ); + } } export function resolvePartLinks( diff --git a/src/lib/converter/comments/parser.ts b/src/lib/converter/comments/parser.ts index df89e06ab..af65774ea 100644 --- a/src/lib/converter/comments/parser.ts +++ b/src/lib/converter/comments/parser.ts @@ -1,16 +1,23 @@ -import { ok } from "assert"; +import assert, { ok } from "assert"; +import { parseDocument as parseYamlDoc } from "yaml"; import type { CommentParserConfig } from "."; import { Comment, - CommentDisplayPart, + type CommentDisplayPart, CommentTag, - InlineTagDisplayPart, + type InlineTagDisplayPart, } from "../../models"; -import { assertNever, Logger, removeIf } from "../../utils"; +import { assertNever, type Logger, removeIf } from "../../utils"; import type { MinimalSourceFile } from "../../utils/minimalSourceFile"; import { nicePath } from "../../utils/paths"; -import { Token, TokenSyntaxKind } from "./lexer"; +import { type Token, TokenSyntaxKind } from "./lexer"; import { extractTagName } from "./tagName"; +import type { + TranslatedString, + TranslationProxy, +} from "../../internationalization/internationalization"; +import { FileRegistry } from "../../models/FileRegistry"; +import { textContent, TextParserReentryState } from "./textParser"; interface LookaheadGenerator { done(): boolean; @@ -65,33 +72,181 @@ export function parseComment( config: CommentParserConfig, file: MinimalSourceFile, logger: Logger, + files: FileRegistry, ): Comment { const lexer = makeLookaheadGenerator(tokens); const tok = lexer.done() || lexer.peek(); const comment = new Comment(); - comment.summary = blockContent(comment, lexer, config, warningImpl); + comment.sourcePath = file.fileName; + comment.summary = blockContent( + comment, + lexer, + config, + logger.i18n, + warningImpl, + files, + ); while (!lexer.done()) { - comment.blockTags.push(blockTag(comment, lexer, config, warningImpl)); + comment.blockTags.push( + blockTag(comment, lexer, config, logger.i18n, warningImpl, files), + ); } - postProcessComment(comment, (message) => { - ok(typeof tok === "object"); - logger.warn( - `${message} in comment at ${nicePath(file.fileName)}:${ - file.getLineAndCharacterOfPosition(tok.pos).line + 1 + const tok2 = tok as Token; + + postProcessComment( + comment, + logger.i18n, + () => + `${nicePath(file.fileName)}:${ + file.getLineAndCharacterOfPosition(tok2.pos).line + 1 }`, - ); - }); + (message) => logger.warn(message), + ); return comment; - function warningImpl(message: string, token: Token) { + function warningImpl(message: TranslatedString, token: Token) { + if ( + config.suppressCommentWarningsInDeclarationFiles && + file.fileName.endsWith(".d.ts") + ) { + return; + } logger.warn(message, token.pos, file); } } +/** + * Intended for parsing markdown documents. This only parses code blocks and + * inline tags outside of code blocks, everything else is text. + * + * If you change this, also look at blockContent, as it likely needs similar + * modifications to ensure parsing is consistent. + */ +export function parseCommentString( + tokens: Generator, + config: CommentParserConfig, + file: MinimalSourceFile, + logger: Logger, + files: FileRegistry, +) { + const suppressWarningsConfig: CommentParserConfig = { + ...config, + jsDocCompatibility: { + defaultTag: true, + exampleTag: true, + ignoreUnescapedBraces: true, + inheritDocTag: true, + }, + suppressCommentWarningsInDeclarationFiles: true, + }; + + const reentry = new TextParserReentryState(); + const content: CommentDisplayPart[] = []; + const lexer = makeLookaheadGenerator(tokens); + + let atNewLine = false; + while (!lexer.done()) { + let consume = true; + const next = lexer.peek(); + reentry.checkState(next); + + switch (next.kind) { + case TokenSyntaxKind.TypeAnnotation: + // Shouldn't have been produced by our lexer + assert(false, "Should be unreachable"); + break; + case TokenSyntaxKind.NewLine: + case TokenSyntaxKind.Text: + case TokenSyntaxKind.Tag: + case TokenSyntaxKind.CloseBrace: + textContent( + file.fileName, + next, + logger.i18n, + (msg, token) => logger.warn(msg, token.pos, file), + content, + files, + atNewLine, + reentry, + ); + break; + + case TokenSyntaxKind.Code: + content.push({ kind: "code", text: next.text }); + break; + + case TokenSyntaxKind.OpenBrace: + inlineTag( + lexer, + content, + suppressWarningsConfig, + logger.i18n, + (message, token) => logger.warn(message, token.pos, file), + ); + consume = false; + break; + + default: + assertNever(next.kind); + } + + atNewLine = next.kind === TokenSyntaxKind.NewLine; + if (consume) { + lexer.take(); + } + } + + // Check for frontmatter + let frontmatterData: Record = {}; + const firstBlock = content[0]; + if (firstBlock.text.startsWith("---\n")) { + const end = firstBlock.text.indexOf("\n---\n"); + if (end !== -1) { + const yamlText = firstBlock.text.slice("---\n".length, end); + firstBlock.text = firstBlock.text + .slice(end + "\n---\n".length) + .trimStart(); + + const frontmatter = parseYamlDoc(yamlText, { prettyErrors: false }); + for (const warning of frontmatter.warnings) { + // Can't translate issues coming from external library... + logger.warn( + warning.message as TranslatedString, + warning.pos[0] + "---\n".length, + file, + ); + } + for (const error of frontmatter.errors) { + // Can't translate issues coming from external library... + logger.error( + error.message as TranslatedString, + error.pos[0] + "---\n".length, + file, + ); + } + + if (frontmatter.errors.length === 0) { + const data = frontmatter.toJS(); + if (typeof data === "object") { + frontmatterData = data; + } else { + logger.error( + logger.i18n.yaml_frontmatter_not_an_object(), + 5, + file, + ); + } + } + } + } + + return { content, frontmatter: frontmatterData }; +} + const HAS_USER_IDENTIFIER: `@${string}`[] = [ "@callback", "@param", @@ -111,7 +266,12 @@ function makeCodeBlock(text: string) { * Loop over comment, produce lint warnings, and set tag names for tags * which have them. */ -function postProcessComment(comment: Comment, warning: (msg: string) => void) { +function postProcessComment( + comment: Comment, + i18n: TranslationProxy, + getPosition: () => string, + warning: (msg: TranslatedString) => void, +) { for (const tag of comment.blockTags) { if (HAS_USER_IDENTIFIER.includes(tag.tag) && tag.content.length) { const first = tag.content[0]; @@ -134,7 +294,9 @@ function postProcessComment(comment: Comment, warning: (msg: string) => void) { ) ) { warning( - "An inline @inheritDoc tag should not appear within a block tag as it will not be processed", + i18n.inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0( + getPosition(), + ), ); } } @@ -142,7 +304,9 @@ function postProcessComment(comment: Comment, warning: (msg: string) => void) { const remarks = comment.blockTags.filter((tag) => tag.tag === "@remarks"); if (remarks.length > 1) { warning( - "At most one @remarks tag is expected in a comment, ignoring all but the first", + i18n.at_most_one_remarks_tag_expected_in_comment_at_0( + getPosition(), + ), ); removeIf(comment.blockTags, (tag) => remarks.indexOf(tag) > 0); } @@ -150,7 +314,9 @@ function postProcessComment(comment: Comment, warning: (msg: string) => void) { const returns = comment.blockTags.filter((tag) => tag.tag === "@returns"); if (remarks.length > 1) { warning( - "At most one @returns tag is expected in a comment, ignoring all but the first", + i18n.at_most_one_returns_tag_expected_in_comment_at_0( + getPosition(), + ), ); removeIf(comment.blockTags, (tag) => returns.indexOf(tag) > 0); } @@ -164,7 +330,9 @@ function postProcessComment(comment: Comment, warning: (msg: string) => void) { if (inlineInheritDoc.length + inheritDoc.length > 1) { warning( - "At most one @inheritDoc tag is expected in a comment, ignoring all but the first", + i18n.at_most_one_inheritdoc_tag_expected_in_comment_at_0( + getPosition(), + ), ); const allInheritTags = [...inlineInheritDoc, ...inheritDoc]; removeIf(comment.summary, (part) => allInheritTags.indexOf(part) > 0); @@ -178,13 +346,17 @@ function postProcessComment(comment: Comment, warning: (msg: string) => void) { ) ) { warning( - "Content in the summary section will be overwritten by the @inheritDoc tag", + i18n.content_in_summary_overwritten_by_inheritdoc_in_comment_at_0( + getPosition(), + ), ); } if ((inlineInheritDoc.length || inheritDoc.length) && remarks.length) { warning( - "Content in the @remarks block will be overwritten by the @inheritDoc tag", + i18n.content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0( + getPosition(), + ), ); } } @@ -195,7 +367,9 @@ function blockTag( comment: Comment, lexer: LookaheadGenerator, config: CommentParserConfig, - warning: (msg: string, token: Token) => void, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, + files: FileRegistry, ): CommentTag { const blockTag = lexer.take(); ok( @@ -203,18 +377,29 @@ function blockTag( "blockTag called not at the start of a block tag.", ); // blockContent is broken if this fails. + if (!config.blockTags.has(blockTag.text)) { + warning(i18n.unknown_block_tag_0(blockTag.text), blockTag); + } + const tagName = aliasedTags.get(blockTag.text) || blockTag.text; let content: CommentDisplayPart[]; if (tagName === "@example") { - return exampleBlock(comment, lexer, config, warning); + return exampleBlock(comment, lexer, config, i18n, warning, files); } else if ( ["@default", "@defaultValue"].includes(tagName) && config.jsDocCompatibility.defaultTag ) { - content = defaultBlockContent(comment, lexer, config, warning); + content = defaultBlockContent( + comment, + lexer, + config, + i18n, + warning, + files, + ); } else { - content = blockContent(comment, lexer, config, warning); + content = blockContent(comment, lexer, config, i18n, warning, files); } return new CommentTag(tagName as `@${string}`, content); @@ -228,15 +413,29 @@ function defaultBlockContent( comment: Comment, lexer: LookaheadGenerator, config: CommentParserConfig, - warning: (msg: string, token: Token) => void, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, + files: FileRegistry, ): CommentDisplayPart[] { lexer.mark(); - const content = blockContent(comment, lexer, config, () => {}); + const tempRegistry = new FileRegistry(); + const content = blockContent( + comment, + lexer, + config, + i18n, + () => {}, + tempRegistry, + ); const end = lexer.done() || lexer.peek(); lexer.release(); - if (content.some((part) => part.kind === "code")) { - return blockContent(comment, lexer, config, warning); + if ( + content.some( + (part) => part.kind === "code" || part.kind === "inline-tag", + ) + ) { + return blockContent(comment, lexer, config, i18n, warning, files); } const tokens: Token[] = []; @@ -267,10 +466,20 @@ function exampleBlock( comment: Comment, lexer: LookaheadGenerator, config: CommentParserConfig, - warning: (msg: string, token: Token) => void, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, + files: FileRegistry, ): CommentTag { lexer.mark(); - const content = blockContent(comment, lexer, config, () => {}); + const tempRegistry = new FileRegistry(); + const content = blockContent( + comment, + lexer, + config, + i18n, + () => {}, + tempRegistry, + ); const end = lexer.done() || lexer.peek(); lexer.release(); @@ -307,11 +516,7 @@ function exampleBlock( case TokenSyntaxKind.CloseBrace: case TokenSyntaxKind.OpenBrace: if (!warnedAboutRichNameContent) { - warning( - "The first line of an example tag will be taken literally as" + - " the example name, and should only contain text.", - lexer.peek(), - ); + warning(i18n.example_tag_literal_name(), lexer.peek()); warnedAboutRichNameContent = true; } exampleName += lexer.take().text; @@ -321,7 +526,14 @@ function exampleBlock( } } - const content = blockContent(comment, lexer, config, warning); + const content = blockContent( + comment, + lexer, + config, + i18n, + warning, + files, + ); const tag = new CommentTag("@example", content); if (exampleName.trim()) { tag.name = exampleName.trim(); @@ -360,25 +572,45 @@ function exampleBlock( } } +/** + * If you change this, also look at parseCommentString as it + * likely needs similar modifications to ensure parsing is consistent. + */ function blockContent( comment: Comment, lexer: LookaheadGenerator, config: CommentParserConfig, - warning: (msg: string, token: Token) => void, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, + files: FileRegistry, ): CommentDisplayPart[] { const content: CommentDisplayPart[] = []; - let atNewLine = true; + let atNewLine = true as boolean; + const reentry = new TextParserReentryState(); loop: while (!lexer.done()) { const next = lexer.peek(); + reentry.checkState(next); let consume = true; switch (next.kind) { case TokenSyntaxKind.NewLine: - case TokenSyntaxKind.Text: content.push({ kind: "text", text: next.text }); break; + case TokenSyntaxKind.Text: + textContent( + comment.sourcePath!, + next, + i18n, + warning, + /*out*/ content, + files, + atNewLine, + reentry, + ); + break; + case TokenSyntaxKind.Code: content.push({ kind: "code", text: next.text }); break; @@ -387,7 +619,7 @@ function blockContent( if (next.text === "@inheritdoc") { if (!config.jsDocCompatibility.inheritDocTag) { warning( - "The @inheritDoc tag should be properly capitalized", + i18n.inheritdoc_tag_properly_capitalized(), next, ); } @@ -400,7 +632,7 @@ function blockContent( // Treat unknown tag as a modifier, but warn about it. comment.modifierTags.add(next.text as `@${string}`); warning( - `Treating unrecognized tag "${next.text}" as a modifier tag`, + i18n.treating_unrecognized_tag_0_as_modifier(next.text), next, ); break; @@ -417,13 +649,13 @@ function blockContent( case TokenSyntaxKind.CloseBrace: // Unmatched closing brace, generate a warning, and treat it as text. if (!config.jsDocCompatibility.ignoreUnescapedBraces) { - warning(`Unmatched closing brace`, next); + warning(i18n.unmatched_closing_brace(), next); } content.push({ kind: "text", text: next.text }); break; case TokenSyntaxKind.OpenBrace: - inlineTag(lexer, content, config, warning); + inlineTag(lexer, content, config, i18n, warning); consume = false; break; @@ -469,7 +701,8 @@ function inlineTag( lexer: LookaheadGenerator, block: CommentDisplayPart[], config: CommentParserConfig, - warning: (msg: string, token: Token) => void, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, ) { const openBrace = lexer.take(); @@ -481,10 +714,7 @@ function inlineTag( ![TokenSyntaxKind.Text, TokenSyntaxKind.Tag].includes(lexer.peek().kind) ) { if (!config.jsDocCompatibility.ignoreUnescapedBraces) { - warning( - "Encountered an unescaped open brace without an inline tag", - openBrace, - ); + warning(i18n.unescaped_open_brace_without_inline_tag(), openBrace); } block.push({ kind: "text", text: openBrace.text }); return; @@ -499,10 +729,7 @@ function inlineTag( lexer.peek().kind != TokenSyntaxKind.Tag)) ) { if (!config.jsDocCompatibility.ignoreUnescapedBraces) { - warning( - "Encountered an unescaped open brace without an inline tag", - openBrace, - ); + warning(i18n.unescaped_open_brace_without_inline_tag(), openBrace); } block.push({ kind: "text", text: openBrace.text + tagName.text }); return; @@ -513,7 +740,7 @@ function inlineTag( } if (!config.inlineTags.has(tagName.text)) { - warning(`Encountered an unknown inline tag "${tagName.text}"`, tagName); + warning(i18n.unknown_inline_tag_0(tagName.text), tagName); } const content: string[] = []; @@ -523,17 +750,14 @@ function inlineTag( while (!lexer.done() && lexer.peek().kind !== TokenSyntaxKind.CloseBrace) { const token = lexer.take(); if (token.kind === TokenSyntaxKind.OpenBrace) { - warning( - "Encountered an open brace within an inline tag, this is likely a mistake", - token, - ); + warning(i18n.open_brace_within_inline_tag(), token); } content.push(token.kind === TokenSyntaxKind.NewLine ? " " : token.text); } if (lexer.done()) { - warning("Inline tag is not closed", openBrace); + warning(i18n.inline_tag_not_closed(), openBrace); } else { lexer.take(); // Close brace } diff --git a/src/lib/converter/comments/rawLexer.ts b/src/lib/converter/comments/rawLexer.ts index 7b7eb8a93..fb8c50be6 100644 --- a/src/lib/converter/comments/rawLexer.ts +++ b/src/lib/converter/comments/rawLexer.ts @@ -1,15 +1,25 @@ -import { Token, TokenSyntaxKind } from "./lexer"; - +import { type Token, TokenSyntaxKind } from "./lexer"; + +/** + * Note: This lexer intentionally *only* recognizes inline tags and code blocks. + * This is because it is intended for use on markdown documents, and we shouldn't + * take some stray `@user` mention within a "Thanks" section of someone's changelog + * as starting a block! + */ export function* lexCommentString( file: string, ): Generator { // Wrapper around our real lex function to collapse adjacent text tokens. let textToken: Token | undefined; for (const token of lexCommentString2(file)) { - if (token.kind === TokenSyntaxKind.Text) { + if ( + token.kind === TokenSyntaxKind.Text || + token.kind === TokenSyntaxKind.NewLine + ) { if (textToken) { textToken.text += token.text; } else { + token.kind = TokenSyntaxKind.Text; textToken = token; } } else { @@ -44,7 +54,7 @@ function* lexCommentString2( } let lineStart = true; - let braceStartsType = false; + let expectingTag = false; for (;;) { if (pos >= end) { @@ -59,23 +69,17 @@ function* lexCommentString2( case "\n": yield makeToken(TokenSyntaxKind.NewLine, 1); lineStart = true; + expectingTag = false; break; case "{": - if (braceStartsType && nextNonWs(pos + 1) !== "@") { - yield makeToken( - TokenSyntaxKind.TypeAnnotation, - findEndOfType(pos) - pos, - ); - braceStartsType = false; - } else { - yield makeToken(TokenSyntaxKind.OpenBrace, 1); - } + yield makeToken(TokenSyntaxKind.OpenBrace, 1); + expectingTag = true; break; case "}": yield makeToken(TokenSyntaxKind.CloseBrace, 1); - braceStartsType = false; + expectingTag = false; break; case "`": { @@ -84,7 +88,6 @@ function* lexCommentString2( // 2. Code block: <3 ticks>\n\n<3 ticks>\n // 3. Unmatched tick(s), not code, but part of some text. // We don't quite handle #2 correctly yet. PR welcome! - braceStartsType = false; let tickCount = 1; let lookahead = pos; @@ -107,6 +110,7 @@ function* lexCommentString2( text: codeText.join(""), pos, }; + expectingTag = false; pos = lookahead; break; } else if (file[lookahead] === "`") { @@ -141,9 +145,11 @@ function* lexCommentString2( text: codeText.join(""), pos, }; + expectingTag = false; pos = lookahead; } else { yield makeToken(TokenSyntaxKind.Text, tickCount); + expectingTag = false; } } @@ -166,10 +172,10 @@ function* lexCommentString2( } if ( + expectingTag && lookahead !== pos + 1 && (lookahead === end || /[\s}]/.test(file[lookahead])) ) { - braceStartsType = true; yield makeToken(TokenSyntaxKind.Tag, lookahead - pos); break; } @@ -212,7 +218,7 @@ function* lexCommentString2( textParts.push(file.substring(lookaheadStart, lookahead)); if (textParts.some((part) => /\S/.test(part))) { - braceStartsType = false; + expectingTag = false; } // This piece of text had line continuations or escaped text @@ -245,64 +251,4 @@ function* lexCommentString2( return file.startsWith("`".repeat(n), pos) && file[pos + n] !== "`"; } - - function findEndOfType(pos: number): number { - let openBraces = 0; - - while (pos < end) { - if (file[pos] === "{") { - openBraces++; - } else if (file[pos] === "}") { - if (--openBraces === 0) { - break; - } - } else if ("`'\"".includes(file[pos])) { - pos = findEndOfString(pos); - } - - pos++; - } - - if (pos < end && file[pos] === "}") { - pos++; - } - - return pos; - } - - function findEndOfString(pos: number): number { - const endOfString = file[pos]; - pos++; - while (pos < end) { - if (file[pos] === endOfString) { - break; - } else if (file[pos] === "\\") { - pos++; // Skip escaped character - } else if ( - endOfString === "`" && - file[pos] === "$" && - file[pos + 1] === "{" - ) { - // Template literal with data inside a ${} - while (pos < end && file[pos] !== "}") { - if ("`'\"".includes(file[pos])) { - pos = findEndOfString(pos) + 1; - } else { - pos++; - } - } - } - - pos++; - } - - return pos; - } - - function nextNonWs(pos: number): string | undefined { - while (pos < end && /\s/.test(file[pos])) { - pos++; - } - return file[pos]; - } } diff --git a/src/lib/converter/comments/textParser.ts b/src/lib/converter/comments/textParser.ts new file mode 100644 index 000000000..4662de653 --- /dev/null +++ b/src/lib/converter/comments/textParser.ts @@ -0,0 +1,327 @@ +/** + * Parser to handle plain text markdown. + * + * Responsible for recognizing relative paths within the text and turning + * them into references. + * @module + */ +import type { + TranslationProxy, + TranslatedString, +} from "../../internationalization"; +import type { CommentDisplayPart } from "../../models"; +import type { FileRegistry } from "../../models/FileRegistry"; +import { HtmlAttributeParser, ParserState } from "../../utils/html"; +import { type Token, TokenSyntaxKind } from "./lexer"; + +import MarkdownIt from "markdown-it"; +const MdHelpers = new MarkdownIt().helpers; + +interface TextParserData { + sourcePath: string; + token: Token; + pos: number; + i18n: TranslationProxy; + warning: (msg: TranslatedString, token: Token) => void; + files: FileRegistry; + atNewLine: boolean; +} + +interface RelativeLink { + pos: number; + end: number; + /** May be undefined if the registry can't find this file */ + target: number | undefined; +} + +/** + * This is incredibly unfortunate. The comment lexer owns the responsibility + * for splitting up text into text/code, this is totally fine for HTML links + * but for markdown links, ``[`code`](./link)`` is valid, so we need to keep + * track of state across calls to {@link textContent}. + */ +export class TextParserReentryState { + withinLinkLabel = false; + private lastPartWasNewline = false; + + checkState(token: Token) { + switch (token.kind) { + case TokenSyntaxKind.Code: + if (/\n\s*\n/.test(token.text)) { + this.withinLinkLabel = false; + } + break; + case TokenSyntaxKind.NewLine: + if (this.lastPartWasNewline) { + this.withinLinkLabel = false; + } + break; + } + + this.lastPartWasNewline = token.kind === TokenSyntaxKind.NewLine; + } +} + +/** + * Look for relative links within a piece of text and add them to the {@link FileRegistry} + * so that they can be correctly resolved during rendering. + */ +export function textContent( + sourcePath: string, + token: Token, + i18n: TranslationProxy, + warning: (msg: TranslatedString, token: Token) => void, + outContent: CommentDisplayPart[], + files: FileRegistry, + atNewLine: boolean, + reentry: TextParserReentryState, +) { + let lastPartEnd = 0; + const data: TextParserData = { + sourcePath, + token, + pos: 0, // relative to the token + i18n, + warning, + files: files, + atNewLine, + }; + + function addRef(ref: RelativeLink) { + outContent.push({ + kind: "text", + text: token.text.slice(lastPartEnd, ref.pos), + }); + outContent.push({ + kind: "relative-link", + text: token.text.slice(ref.pos, ref.end), + target: ref.target, + }); + lastPartEnd = ref.end; + data.pos = ref.end; + if (!ref.target) { + warning( + i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output( + token.text.slice(ref.pos, ref.end), + ), + { + kind: TokenSyntaxKind.Text, + // ref.pos is relative to the token, but this pos is relative to the file. + pos: token.pos + ref.pos, + text: token.text.slice(ref.pos, ref.end), + }, + ); + } + } + + while (data.pos < token.text.length) { + const link = checkMarkdownLink(data, reentry); + if (link) { + addRef(link); + continue; + } + + const reference = checkReference(data); + if (reference) { + addRef(reference); + continue; + } + + const tagLink = checkTagLink(data); + if (tagLink) { + addRef(tagLink); + continue; + } + + ++data.pos; + } + + if (lastPartEnd !== token.text.length) { + outContent.push({ kind: "text", text: token.text.slice(lastPartEnd) }); + } +} + +/** + * Links are inline text with the form `[ text ]( url title )`. + * + * Images are just links with a leading `!` and lack of support for `[ref]` referring to a path + * defined elsewhere, we don't care about that distinction here as we'll only replace the path + * piece of the image. + * + * Reference: https://github.com/markdown-it/markdown-it/blob/14.1.0/lib/rules_inline/link.mjs + * Reference: https://github.com/markdown-it/markdown-it/blob/14.1.0/lib/rules_inline/image.mjs + * + */ +function checkMarkdownLink( + data: TextParserData, + reentry: TextParserReentryState, +): RelativeLink | undefined { + const { token, sourcePath, files } = data; + + let searchStart: number; + if (reentry.withinLinkLabel) { + 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 (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)) { + return { + pos: labelEnd + 2, + end: link.pos, + target: files.register(sourcePath, link.str), + }; + } + + // 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; + } + } +} + +/** + * Reference definitions are blocks with the form `[label]: link title` + * Reference: https://github.com/markdown-it/markdown-it/blob/14.1.0/lib/rules_block/reference.mjs + * + * Note: This may include false positives where TypeDoc recognizes a reference block that markdown + * does not if users start lines with something that looks like a reference block without fully + * separating it from an above paragraph. For a first cut, this is good enough. + */ +function checkReference(data: TextParserData): RelativeLink | undefined { + const { atNewLine, pos, token, files, sourcePath } = data; + + if (atNewLine) { + let lookahead = pos; + while (/[ \t]/.test(token.text[lookahead])) { + ++lookahead; + } + if (token.text[lookahead] === "[") { + while ( + lookahead < token.text.length && + /[^\n\]]/.test(token.text[lookahead]) + ) { + ++lookahead; + } + if (token.text.startsWith("]:", lookahead)) { + lookahead += 2; + while (/[ \t]/.test(token.text[lookahead])) { + ++lookahead; + } + + const link = MdHelpers.parseLinkDestination( + token.text, + lookahead, + token.text.length, + ); + + if (link.ok) { + if (isRelativePath(link.str)) { + return { + pos: lookahead, + end: link.pos, + target: files.register(sourcePath, link.str), + }; + } + + data.pos = link.pos - 1; + } + } + } + } +} + +/** + * Looks for `
` and `` + */ +function checkTagLink(data: TextParserData): RelativeLink | undefined { + const { pos, token } = data; + + if (token.text.startsWith(" {} +export abstract class ConverterComponent extends AbstractComponent< + Converter, + {} +> {} diff --git a/src/lib/converter/context.ts b/src/lib/converter/context.ts index aa4c8f3b7..9d4cebaef 100644 --- a/src/lib/converter/context.ts +++ b/src/lib/converter/context.ts @@ -2,10 +2,11 @@ import { ok as assert } from "assert"; import ts from "typescript"; import { - Reflection, - ProjectReflection, + type Reflection, + type ProjectReflection, ContainerReflection, DeclarationReflection, + type DocumentReflection, ReflectionKind, ReflectionFlag, } from "../models/index"; @@ -22,6 +23,7 @@ import { getSignatureComment, } from "./comments"; import { getHumanName } from "../utils/tsutils"; +import type { TranslationProxy } from "../internationalization/internationalization"; /** * The context describes the current state the converter is in. @@ -39,6 +41,13 @@ export class Context { return this.program.getTypeChecker(); } + /** + * Translation interface for log messages. + */ + get i18n(): TranslationProxy { + return this.converter.application.i18n; + } + /** * The program currently being converted. * Accessing this property will throw if a source file is not currently being converted. @@ -111,10 +120,12 @@ export class Context { if (!nodeType) { if (node.symbol) { nodeType = this.checker.getDeclaredTypeOfSymbol(node.symbol); + // The TS types lie due to ts.SourceFile } else if (node.parent?.symbol) { nodeType = this.checker.getDeclaredTypeOfSymbol( node.parent.symbol, ); + // The TS types lie due to ts.SourceFile } else if (node.parent?.parent?.symbol) { nodeType = this.checker.getDeclaredTypeOfSymbol( node.parent.parent.symbol, @@ -209,7 +220,13 @@ export class Context { if (exportSymbol) { this.registerReflection(reflection, exportSymbol); } - this.registerReflection(reflection, symbol); + + const path = reflection.kindOf( + ReflectionKind.Namespace | ReflectionKind.Module, + ) + ? symbol?.declarations?.find(ts.isSourceFile)?.fileName + : undefined; + this.project.registerReflection(reflection, symbol, path); } finalizeDeclarationReflection(reflection: DeclarationReflection) { @@ -218,12 +235,15 @@ export class Context { this, reflection, ); + + if (reflection.kindOf(ReflectionKind.MayContainDocuments)) { + this.converter.processDocumentTags(reflection, reflection); + } } - addChild(reflection: DeclarationReflection) { + addChild(reflection: DeclarationReflection | DocumentReflection) { if (this.scope instanceof ContainerReflection) { - this.scope.children ??= []; - this.scope.children.push(reflection); + this.scope.addChild(reflection); } } @@ -239,20 +259,7 @@ export class Context { * @param symbol The symbol the given reflection was resolved from. */ registerReflection(reflection: Reflection, symbol: ts.Symbol | undefined) { - this.project.registerReflection(reflection, symbol); - } - - /** - * Trigger a node reflection event. - * - * All events are dispatched on the current converter instance. - * - * @param name The name of the event that should be triggered. - * @param reflection The triggering reflection. - * @param node The triggering TypeScript node if available. - */ - trigger(name: string, reflection: Reflection, node?: ts.Node) { - this.converter.trigger(name, this, reflection, node); + this.project.registerReflection(reflection, symbol, void 0); } /** @internal */ @@ -266,19 +273,19 @@ export class Context { kind, this.converter.config, this.logger, - this.converter.commentStyle, - this.converter.useTsLinkResolution ? this.checker : undefined, + this.checker, + this.project.files, ); } - getNodeComment(node: ts.Node, kind: ReflectionKind) { + getNodeComment(node: ts.Node, moduleComment: boolean) { return getNodeComment( node, - kind, + moduleComment, this.converter.config, this.logger, - this.converter.commentStyle, - this.converter.useTsLinkResolution ? this.checker : undefined, + this.checker, + this.project.files, ); } @@ -287,8 +294,8 @@ export class Context { node, this.converter.config, this.logger, - this.converter.commentStyle, - this.converter.useTsLinkResolution ? this.checker : undefined, + this.checker, + this.project.files, ); } @@ -304,7 +311,8 @@ export class Context { declaration, this.converter.config, this.logger, - this.converter.useTsLinkResolution ? this.checker : undefined, + this.checker, + this.project.files, ); } @@ -315,8 +323,8 @@ export class Context { declaration, this.converter.config, this.logger, - this.converter.commentStyle, - this.converter.useTsLinkResolution ? this.checker : undefined, + this.checker, + this.project.files, ); } diff --git a/src/lib/converter/converter.ts b/src/lib/converter/converter.ts index 39641f073..ed695ee97 100644 --- a/src/lib/converter/converter.ts +++ b/src/lib/converter/converter.ts @@ -3,21 +3,33 @@ import ts from "typescript"; import type { Application } from "../application"; import { Comment, - CommentDisplayPart, + type CommentDisplayPart, + type ContainerReflection, + type DeclarationReflection, + DocumentReflection, + type ParameterReflection, ProjectReflection, - Reflection, + type Reflection, ReflectionKind, - ReflectionSymbolId, - SomeType, + type ReflectionSymbolId, + type SignatureReflection, + type SomeType, + type TypeParameterReflection, } from "../models/index"; import { Context } from "./context"; import { ConverterComponent } from "./components"; import { Component, ChildableComponent } from "../utils/component"; -import { Option, MinimalSourceFile, readFile, unique } from "../utils"; +import { + Option, + MinimalSourceFile, + readFile, + unique, + getDocumentEntryPoints, +} from "../utils"; import { convertType } from "./types"; import { ConverterEvents } from "./converter-events"; import { convertSymbol } from "./symbols"; -import { createMinimatch, matchesAny } from "../utils/paths"; +import { createMinimatch, matchesAny, nicePath } from "../utils/paths"; import type { Minimatch } from "minimatch"; import { hasAllFlags, hasAnyFlag } from "../utils/enum"; import type { DocumentationEntryPoint } from "../utils/entry-point"; @@ -26,15 +38,45 @@ import type { CommentStyle, ValidationOptions, } from "../utils/options/declaration"; -import { parseComment } from "./comments/parser"; +import { parseCommentString } from "./comments/parser"; import { lexCommentString } from "./comments/rawLexer"; import { resolvePartLinks, resolveLinks, - ExternalSymbolResolver, - ExternalResolveResult, + type ExternalSymbolResolver, + type ExternalResolveResult, } from "./comments/linkResolver"; -import type { DeclarationReference } from "./comments/declarationReference"; +import { + meaningToString, + type DeclarationReference, +} from "./comments/declarationReference"; +import { basename, dirname, resolve } from "path"; +import type { FileRegistry } from "../models/FileRegistry"; + +export interface ConverterEvents { + begin: [Context]; + end: [Context]; + createDeclaration: [Context, DeclarationReflection]; + createSignature: [ + Context, + SignatureReflection, + ( + | ts.SignatureDeclaration + | ts.IndexSignatureDeclaration + | ts.JSDocSignature + )?, + ts.Signature?, + ]; + createParameter: [Context, ParameterReflection, ts.Node?]; + createTypeParameter: [ + Context, + TypeParameterReflection, + ts.TypeParameterDeclaration?, + ]; + resolveBegin: [Context]; + resolveReflection: [Context, Reflection]; + resolveEnd: [Context]; +} /** * Compiles source files using TypeScript and converts compiler symbols to reflections. @@ -46,7 +88,8 @@ import type { DeclarationReference } from "./comments/declarationReference"; }) export class Converter extends ChildableComponent< Application, - ConverterComponent + ConverterComponent, + ConverterEvents > { /** @internal */ @Option("externalPattern") @@ -89,10 +132,6 @@ export class Converter extends ChildableComponent< Record >; - /** @internal */ - @Option("useTsLinkResolution") - accessor useTsLinkResolution!: boolean; - /** @internal */ @Option("preserveLinkText") accessor preserveLinkText!: boolean; @@ -208,7 +247,7 @@ export class Converter extends ChildableComponent< name += ref.symbolReference.path.map((p) => p.path).join("."); } if (ref.symbolReference.meaning) { - name += ":" + ref.symbolReference.meaning; + name += meaningToString(ref.symbolReference.meaning); } if (typeof modLinks[name] === "string") { @@ -231,11 +270,13 @@ export class Converter extends ChildableComponent< const project = new ProjectReflection( this.application.options.getValue("name"), + this.application.files, ); const context = new Context(this, programs, project); this.trigger(Converter.EVENT_BEGIN, context); + this.addProjectDocuments(project); this.compile(entryPoints, context); this.resolve(context); @@ -245,6 +286,28 @@ export class Converter extends ChildableComponent< return project; } + /** @internal */ + addProjectDocuments(project: ProjectReflection) { + const projectDocuments = getDocumentEntryPoints( + this.application.logger, + this.application.options, + ); + for (const { displayName, path } of projectDocuments) { + let file: MinimalSourceFile; + try { + file = new MinimalSourceFile(readFile(path), path); + } catch (error: any) { + this.application.logger.error( + this.application.logger.i18n.failed_to_read_0_when_processing_project_document( + path, + ), + ); + continue; + } + this.addDocument(project, file, displayName); + } + } + /** @internal */ convertSymbol( context: Context, @@ -271,12 +334,13 @@ export class Converter extends ChildableComponent< /** * Parse the given file into a comment. Intended to be used with markdown files. */ - parseRawComment(file: MinimalSourceFile) { - return parseComment( + parseRawComment(file: MinimalSourceFile, files: FileRegistry) { + return parseCommentString( lexCommentString(file.text), this.config, file, this.application.logger, + files, ); } @@ -291,7 +355,6 @@ export class Converter extends ChildableComponent< * reference passed will have the `moduleSource` set and the `symbolReference` will navigate via `.`) * and user defined \{\@link\} tags which cannot be resolved. If the link being resolved is inferred * from a type, then no `part` will be passed to the resolver function. - * @since 0.22.14 */ addUnknownSymbolResolver(resolver: ExternalSymbolResolver): void { this._externalSymbolResolvers.push(resolver); @@ -354,12 +417,21 @@ export class Converter extends ChildableComponent< context: undefined as Context | undefined, }; }); + + let createModuleReflections = entries.length > 1; + if (!createModuleReflections) { + const opts = this.application.options; + createModuleReflections = opts.isSet("alwaysCreateEntryPointModule") + ? opts.getValue("alwaysCreateEntryPointModule") + : !!(context.scope as ProjectReflection).documents; + } + entries.forEach((e) => { context.setActiveProgram(e.entryPoint.program); e.context = this.convertExports( context, e.entryPoint, - entries.length === 1, + createModuleReflections, ); }); for (const { entryPoint, context } of entries) { @@ -375,24 +447,25 @@ export class Converter extends ChildableComponent< private convertExports( context: Context, entryPoint: DocumentationEntryPoint, - singleEntryPoint: boolean, + createModuleReflections: boolean, ) { const node = entryPoint.sourceFile; const entryName = entryPoint.displayName; const symbol = getSymbolForModuleLike(context, node); let moduleContext: Context; - if (singleEntryPoint) { + if (createModuleReflections === false) { // Special case for when we're giving a single entry point, we don't need to // create modules for each entry. Register the project as this module. - context.project.registerReflection(context.project, symbol); + context.project.registerReflection( + context.project, + symbol, + entryPoint.sourceFile.fileName, + ); context.project.comment = symbol ? context.getComment(symbol, context.project.kind) : context.getFileComment(node); - context.trigger( - Converter.EVENT_CREATE_DECLARATION, - context.project, - ); + this.processDocumentTags(context.project, context.project); moduleContext = context; } else { const reflection = context.createDeclarationReflection( @@ -408,23 +481,11 @@ export class Converter extends ChildableComponent< if (entryPoint.readmeFile) { const readme = readFile(entryPoint.readmeFile); - const comment = this.parseRawComment( + const { content } = this.parseRawComment( new MinimalSourceFile(readme, entryPoint.readmeFile), + context.project.files, ); - - if (comment.blockTags.length || comment.modifierTags.size) { - const ignored = [ - ...comment.blockTags.map((tag) => tag.tag), - ...comment.modifierTags, - ]; - context.logger.warn( - `Block and modifier tags will be ignored within the readme:\n\t${ignored.join( - "\n\t", - )}`, - ); - } - - reflection.readme = comment.summary; + reflection.readme = content; } reflection.packageVersion = entryPoint.version; @@ -512,6 +573,111 @@ export class Converter extends ChildableComponent< ); } + processDocumentTags(reflection: Reflection, parent: ContainerReflection) { + let relativeTo = reflection.comment?.sourcePath; + if (relativeTo) { + relativeTo = dirname(relativeTo); + const tags = reflection.comment?.getTags("@document") || []; + reflection.comment?.removeTags("@document"); + for (const tag of tags) { + const path = Comment.combineDisplayParts(tag.content); + + let file: MinimalSourceFile; + try { + const resolved = resolve(relativeTo, path); + file = new MinimalSourceFile(readFile(resolved), resolved); + } catch { + this.application.logger.warn( + this.application.logger.i18n.failed_to_read_0_when_processing_document_tag_in_1( + nicePath(path), + nicePath(reflection.comment!.sourcePath!), + ), + ); + continue; + } + + this.addDocument( + parent, + file, + basename(file.fileName).replace(/\.[^.]+$/, ""), + ); + } + } + } + + private addDocument( + parent: ContainerReflection | DocumentReflection, + file: MinimalSourceFile, + displayName: string, + ) { + const { content, frontmatter } = this.parseRawComment( + file, + parent.project.files, + ); + const children = frontmatter["children"]; + delete frontmatter["children"]; + const docRefl = new DocumentReflection( + displayName, + parent, + content, + frontmatter, + ); + + parent.addChild(docRefl); + parent.project.registerReflection(docRefl, undefined, file.fileName); + + const childrenToAdd: [string, string][] = []; + if (children && typeof children === "object") { + if (Array.isArray(children)) { + for (const child of children) { + if (typeof child === "string") { + childrenToAdd.push([ + basename(child).replace(/\.[^.]+$/, ""), + child, + ]); + } else { + this.application.logger.error( + this.application.i18n.frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values( + nicePath(file.fileName), + ), + ); + return; + } + } + } else { + for (const [name, path] of Object.entries(children)) { + if (typeof path === "string") { + childrenToAdd.push([name, path]); + } else { + this.application.logger.error( + this.application.i18n.frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values( + nicePath(file.fileName), + ), + ); + return; + } + } + } + } + + for (const [displayName, path] of childrenToAdd) { + const absPath = resolve(dirname(file.fileName), path); + let childFile: MinimalSourceFile; + try { + childFile = new MinimalSourceFile(readFile(absPath), absPath); + } catch (error: any) { + this.application.logger.error( + this.application.logger.i18n.failed_to_read_0_when_processing_document_child_in_1( + path, + nicePath(file.fileName), + ), + ); + continue; + } + this.addDocument(docRefl, childFile, displayName); + } + } + private _buildCommentParserConfig() { this._config = { blockTags: new Set(this.application.options.getValue("blockTags")), @@ -523,7 +689,20 @@ export class Converter extends ChildableComponent< ), jsDocCompatibility: this.application.options.getValue("jsDocCompatibility"), + suppressCommentWarningsInDeclarationFiles: + this.application.options.getValue( + "suppressCommentWarningsInDeclarationFiles", + ), + useTsLinkResolution: this.application.options.getValue( + "useTsLinkResolution", + ), + commentStyle: this.application.options.getValue("commentStyle"), }; + + // Can't be included in options because the TSDoc parser blows up if we do. + // TypeDoc supports it as one, so it should always be included here. + this._config.blockTags.add("@inheritDoc"); + return this._config; } } @@ -544,11 +723,8 @@ function getSymbolForModuleLike( const sourceFile = node.getSourceFile(); const globalSymbols = context.checker .getSymbolsInScope(node, ts.SymbolFlags.ModuleMember) - .filter( - (s) => - s - .getDeclarations() - ?.some((d) => d.getSourceFile() === sourceFile), + .filter((s) => + s.getDeclarations()?.some((d) => d.getSourceFile() === sourceFile), ); // Detect declaration files with declare module "foo" as their only export @@ -609,11 +785,10 @@ function getExports( if (globalSymbol) { result = context.checker .getExportsOfModule(globalSymbol) - .filter( - (exp) => - exp.declarations?.some( - (d) => d.getSourceFile() === node, - ), + .filter((exp) => + exp.declarations?.some( + (d) => d.getSourceFile() === node, + ), ); } } @@ -623,11 +798,10 @@ function getExports( const sourceFile = node.getSourceFile(); result = context.checker .getSymbolsInScope(node, ts.SymbolFlags.ModuleMember) - .filter( - (s) => - s - .getDeclarations() - ?.some((d) => d.getSourceFile() === sourceFile), + .filter((s) => + s + .getDeclarations() + ?.some((d) => d.getSourceFile() === sourceFile), ); } diff --git a/src/lib/converter/factories/index-signature.ts b/src/lib/converter/factories/index-signature.ts index 65ade13dc..8ffa1815c 100644 --- a/src/lib/converter/factories/index-signature.ts +++ b/src/lib/converter/factories/index-signature.ts @@ -9,27 +9,24 @@ import { import type { Context } from "../context"; import { ConverterEvents } from "../converter-events"; -export function convertIndexSignature(context: Context, symbol: ts.Symbol) { +export function convertIndexSignatures(context: Context, symbol: ts.Symbol) { assert(context.scope instanceof DeclarationReflection); const indexSymbol = symbol.members?.get("__index" as ts.__String); - if (indexSymbol) { - // Right now TypeDoc models don't have a way to distinguish between string - // and number index signatures... { [x: string]: 1 | 2; [x: number]: 2 } - // will be misrepresented. - const indexDeclaration = indexSymbol.getDeclarations()?.[0]; - assert( - indexDeclaration && - ts.isIndexSignatureDeclaration(indexDeclaration), - ); - const param = indexDeclaration.parameters[0]; + if (!indexSymbol) return; + + for (const indexDeclaration of indexSymbol.getDeclarations() || []) { + assert(ts.isIndexSignatureDeclaration(indexDeclaration)); + const param = indexDeclaration.parameters[0] as + | ts.ParameterDeclaration + | undefined; assert(param && ts.isParameter(param)); const index = new SignatureReflection( "__index", ReflectionKind.IndexSignature, context.scope, ); - index.comment = context.getComment(indexSymbol, index.kind); + index.comment = context.getNodeComment(indexDeclaration, false); index.parameters = [ new ParameterReflection( param.name.getText(), @@ -46,10 +43,12 @@ export function convertIndexSignature(context: Context, symbol: ts.Symbol) { indexDeclaration.type, ); context.registerReflection(index, indexSymbol); - context.scope.indexSignature = index; + context.scope.indexSignatures ||= []; + context.scope.indexSignatures.push(index); - context.trigger( + context.converter.trigger( ConverterEvents.CREATE_SIGNATURE, + context, index, indexDeclaration, ); diff --git a/src/lib/converter/factories/signature.ts b/src/lib/converter/factories/signature.ts index 97490fa1c..32c39cd9b 100644 --- a/src/lib/converter/factories/signature.ts +++ b/src/lib/converter/factories/signature.ts @@ -1,13 +1,12 @@ import ts from "typescript"; import assert from "assert"; import { - ConversionFlags, DeclarationReflection, IntrinsicType, ParameterReflection, PredicateType, ReferenceType, - Reflection, + type Reflection, ReflectionFlag, ReflectionKind, SignatureReflection, @@ -57,9 +56,6 @@ export function createSignature( ); } - // If we are creating signatures for a variable or property and it has a comment associated with it - // then we should prefer that comment over any comment on the signature. The comment plugin - // will copy the comment down if this signature doesn't have one, so don't set one. let parentReflection = context.scope; if ( parentReflection.kindOf(ReflectionKind.TypeLiteral) && @@ -68,15 +64,14 @@ export function createSignature( parentReflection = parentReflection.parent; } - if ( - declaration && - (!parentReflection.comment || - !( - parentReflection.conversionFlags & - ConversionFlags.VariableOrPropertySource - )) - ) { - sigRef.comment = context.getSignatureComment(declaration); + if (declaration) { + const sigComment = context.getSignatureComment(declaration); + if (parentReflection.comment?.discoveryId !== sigComment?.discoveryId) { + sigRef.comment = sigComment; + if (parentReflection.kindOf(ReflectionKind.MayContainDocuments)) { + context.converter.processDocumentTags(sigRef, parentReflection); + } + } } sigRef.typeParameters = convertTypeParameters( @@ -199,9 +194,7 @@ function convertParameters( | undefined, ) { return parameters.map((param, i) => { - const declaration = param.valueDeclaration as - | ts.Declaration - | undefined; + const declaration = param.valueDeclaration; assert( !declaration || ts.isParameter(declaration) || @@ -218,7 +211,11 @@ function convertParameters( paramRefl.comment ||= context.getComment(param, paramRefl.kind); context.registerReflection(paramRefl, param); - context.trigger(ConverterEvents.CREATE_PARAMETER, paramRefl); + context.converter.trigger( + ConverterEvents.CREATE_PARAMETER, + context, + paramRefl, + ); let type: ts.Type | ts.TypeNode | undefined; if (declaration) { @@ -296,7 +293,11 @@ export function convertParameterNodes( paramRefl, context.getSymbolAtLocation(param), ); - context.trigger(ConverterEvents.CREATE_PARAMETER, paramRefl); + context.converter.trigger( + ConverterEvents.CREATE_PARAMETER, + context, + paramRefl, + ); paramRefl.type = context.converter.convertType( context.withScope(paramRefl), @@ -384,7 +385,11 @@ function convertTypeParameters( } context.registerReflection(paramRefl, param.getSymbol()); - context.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl); + context.converter.trigger( + ConverterEvents.CREATE_TYPE_PARAMETER, + context, + paramRefl, + ); return paramRefl; }); @@ -425,7 +430,12 @@ export function createTypeParamReflection( paramRefl.comment = context.getJsDocComment(param.parent); } - context.trigger(ConverterEvents.CREATE_TYPE_PARAMETER, paramRefl, param); + context.converter.trigger( + ConverterEvents.CREATE_TYPE_PARAMETER, + context, + paramRefl, + param, + ); return paramRefl; } @@ -465,8 +475,9 @@ export function convertTemplateParameterNodes( paramRefl.comment = context.getJsDocComment(param.parent); } - context.trigger( + context.converter.trigger( ConverterEvents.CREATE_TYPE_PARAMETER, + context, paramRefl, param, ); diff --git a/src/lib/converter/index.ts b/src/lib/converter/index.ts index c8ab59175..6075baa66 100644 --- a/src/lib/converter/index.ts +++ b/src/lib/converter/index.ts @@ -1,5 +1,5 @@ export { Context } from "./context"; -export { Converter } from "./converter"; +export { Converter, type ConverterEvents } from "./converter"; export type { CommentParserConfig } from "./comments/index"; export { convertDefaultValue, convertExpression } from "./convert-expression"; export type { diff --git a/src/lib/converter/jsdoc.ts b/src/lib/converter/jsdoc.ts index da5b38c8a..64fc30552 100644 --- a/src/lib/converter/jsdoc.ts +++ b/src/lib/converter/jsdoc.ts @@ -123,7 +123,11 @@ function convertJsDocSignature(context: Context, node: ts.JSDocSignature) { context.scope, ); context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); const signature = new SignatureReflection( "__type", diff --git a/src/lib/converter/plugins/CategoryPlugin.ts b/src/lib/converter/plugins/CategoryPlugin.ts index 106b053a6..1f6d87130 100644 --- a/src/lib/converter/plugins/CategoryPlugin.ts +++ b/src/lib/converter/plugins/CategoryPlugin.ts @@ -1,7 +1,8 @@ import { ContainerReflection, - DeclarationReflection, + type DeclarationReflection, Comment, + type DocumentReflection, } from "../../models"; import { ReflectionCategory } from "../../models"; import { Component, ConverterComponent } from "../components"; @@ -16,7 +17,9 @@ import { Option, getSortFunction, removeIf } from "../../utils"; */ @Component({ name: "category" }) export class CategoryPlugin extends ConverterComponent { - sortFunction!: (reflections: DeclarationReflection[]) => void; + sortFunction!: ( + reflections: Array, + ) => void; @Option("defaultCategory") accessor defaultCategory!: string; @@ -40,13 +43,10 @@ export class CategoryPlugin extends ConverterComponent { * Create a new CategoryPlugin instance. */ override initialize() { - this.listenTo( - this.owner, - { - [Converter.EVENT_BEGIN]: this.onBegin, - [Converter.EVENT_RESOLVE_END]: this.onEndResolve, - }, - undefined, + this.owner.on(Converter.EVENT_BEGIN, this.onBegin.bind(this), -200); + this.owner.on( + Converter.EVENT_RESOLVE_END, + this.onEndResolve.bind(this), -200, ); } @@ -61,9 +61,7 @@ export class CategoryPlugin extends ConverterComponent { if (this.defaultCategory) { CategoryPlugin.defaultCategory = this.defaultCategory; } - if (this.categoryOrder) { - CategoryPlugin.WEIGHTS = this.categoryOrder; - } + CategoryPlugin.WEIGHTS = this.categoryOrder; } /** @@ -90,10 +88,9 @@ export class CategoryPlugin extends ConverterComponent { if (unusedBoosts.size) { context.logger.warn( - `Not all categories specified in searchCategoryBoosts were used in the documentation.` + - ` The unused categories were:\n\t${Array.from( - unusedBoosts, - ).join("\n\t")}`, + context.i18n.not_all_search_category_boosts_used_0( + Array.from(unusedBoosts).join("\n\t"), + ), ); } } @@ -117,7 +114,7 @@ export class CategoryPlugin extends ConverterComponent { obj, group.children, ); - if (group.categories && group.categories.length > 1) { + if (group.categories.length > 1) { group.categories.sort(CategoryPlugin.sortCatCallback); } else if ( group.categories.length === 1 && @@ -130,11 +127,14 @@ export class CategoryPlugin extends ConverterComponent { } private lumpCategorize(obj: ContainerReflection) { - if (!obj.children || obj.children.length === 0 || obj.categories) { + if (!obj.childrenIncludingDocuments || obj.categories) { return; } - obj.categories = this.getReflectionCategories(obj, obj.children); - if (obj.categories && obj.categories.length > 1) { + obj.categories = this.getReflectionCategories( + obj, + obj.childrenIncludingDocuments, + ); + if (obj.categories.length > 1) { obj.categories.sort(CategoryPlugin.sortCatCallback); } else if ( obj.categories.length === 1 && @@ -155,7 +155,7 @@ export class CategoryPlugin extends ConverterComponent { */ private getReflectionCategories( parent: ContainerReflection, - reflections: DeclarationReflection[], + reflections: Array, ): ReflectionCategory[] { const categories = new Map(); @@ -189,7 +189,10 @@ export class CategoryPlugin extends ConverterComponent { cat.description = body; } else { this.application.logger.warn( - `Comment for ${parent.getFriendlyFullName()} includes @categoryDescription for "${header}", but no child is placed in that category.`, + this.application.i18n.comment_for_0_includes_categoryDescription_for_1_but_no_child_in_group( + parent.getFriendlyFullName(), + header, + ), ); } @@ -215,19 +218,28 @@ export class CategoryPlugin extends ConverterComponent { * @privateRemarks * If you change this, also update getGroups in GroupPlugin accordingly. */ - private extractCategories(reflection: DeclarationReflection) { + private extractCategories( + reflection: DeclarationReflection | DocumentReflection, + ) { const categories = CategoryPlugin.getCategories(reflection); reflection.comment?.removeTags("@category"); - for (const sig of reflection.getNonIndexSignatures()) { - sig.comment?.removeTags("@category"); - } - - if (reflection.type?.type === "reflection") { - reflection.type.declaration.comment?.removeTags("@category"); - for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + if (reflection.isDeclaration()) { + for (const sig of reflection.getNonIndexSignatures()) { sig.comment?.removeTags("@category"); } + + if (reflection.type?.type === "reflection") { + reflection.type.declaration.comment?.removeTags("@category"); + for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + sig.comment?.removeTags("@category"); + } + } + } + + if (reflection.isDocument() && "category" in reflection.frontmatter) { + categories.add(String(reflection.frontmatter["category"])); + delete reflection.frontmatter["category"]; } categories.delete(""); @@ -274,7 +286,9 @@ export class CategoryPlugin extends ConverterComponent { return aWeight - bWeight; } - static getCategories(reflection: DeclarationReflection) { + static getCategories( + reflection: DeclarationReflection | DocumentReflection, + ) { const categories = new Set(); function discoverCategories(comment: Comment | undefined) { if (!comment) return; @@ -288,15 +302,17 @@ export class CategoryPlugin extends ConverterComponent { } discoverCategories(reflection.comment); - for (const sig of reflection.getNonIndexSignatures()) { - discoverCategories(sig.comment); - } - - if (reflection.type?.type === "reflection") { - discoverCategories(reflection.type.declaration.comment); - for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + if (reflection.isDeclaration()) { + for (const sig of reflection.getNonIndexSignatures()) { discoverCategories(sig.comment); } + + if (reflection.type?.type === "reflection") { + discoverCategories(reflection.type.declaration.comment); + for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + discoverCategories(sig.comment); + } + } } categories.delete(""); diff --git a/src/lib/converter/plugins/CommentPlugin.ts b/src/lib/converter/plugins/CommentPlugin.ts index 2af60f371..27f52f18b 100644 --- a/src/lib/converter/plugins/CommentPlugin.ts +++ b/src/lib/converter/plugins/CommentPlugin.ts @@ -2,18 +2,18 @@ import { Component, ConverterComponent } from "../components"; import { Converter } from "../converter"; import type { Context } from "../context"; import { - Reflection, + type Reflection, ReflectionFlag, ReflectionKind, - TypeParameterReflection, + type TypeParameterReflection, DeclarationReflection, SignatureReflection, - ParameterReflection, + type ParameterReflection, Comment, - ReflectionType, - SourceReference, - TypeVisitor, + type SourceReference, + type TypeVisitor, CommentTag, + ReflectionType, } from "../../models"; import { Option, @@ -24,6 +24,7 @@ import { removeIf, } from "../../utils"; import { CategoryPlugin } from "./CategoryPlugin"; +import { setIntersection } from "../../utils/set"; /** * These tags are not useful to display in the generated documentation. @@ -45,6 +46,19 @@ const NEVER_RENDERED = [ "@this", "@type", "@typedef", + "@jsx", +] as const; + +// We might make this user configurable at some point, but for now, +// this set is configured here. +const MUTUALLY_EXCLUSIVE_MODIFIERS = [ + new Set<`@${string}`>([ + "@alpha", + "@beta", + "@experimental", + "@internal", + "@public", + ]), ] as const; /** @@ -108,6 +122,9 @@ export class CommentPlugin extends ConverterComponent { @Option("excludeTags") accessor excludeTags!: `@${string}`[]; + @Option("cascadedModifierTags") + accessor cascadedModifierTags!: `@${string}`[]; + @Option("excludeInternal") accessor excludeInternal!: boolean; @@ -130,7 +147,7 @@ export class CommentPlugin extends ConverterComponent { private get excludeNotDocumentedKinds(): number { this._excludeKinds ??= this.application.options .getValue("excludeNotDocumentedKinds") - .reduce((a, b) => a | (ReflectionKind[b] as number), 0); + .reduce((a, b) => a | ReflectionKind[b], 0); return this._excludeKinds; } @@ -138,15 +155,25 @@ export class CommentPlugin extends ConverterComponent { * Create a new CommentPlugin instance. */ override initialize() { - this.listenTo(this.owner, { - [Converter.EVENT_CREATE_DECLARATION]: this.onDeclaration, - [Converter.EVENT_CREATE_SIGNATURE]: this.onDeclaration, - [Converter.EVENT_CREATE_TYPE_PARAMETER]: this.onCreateTypeParameter, - [Converter.EVENT_RESOLVE_BEGIN]: this.onBeginResolve, - [Converter.EVENT_RESOLVE]: this.onResolve, - [Converter.EVENT_END]: () => { - this._excludeKinds = undefined; - }, + this.owner.on( + Converter.EVENT_CREATE_DECLARATION, + this.onDeclaration.bind(this), + ); + this.owner.on( + Converter.EVENT_CREATE_SIGNATURE, + this.onDeclaration.bind(this), + ); + this.owner.on( + Converter.EVENT_CREATE_TYPE_PARAMETER, + this.onCreateTypeParameter.bind(this), + ); + this.owner.on( + Converter.EVENT_RESOLVE_BEGIN, + this.onBeginResolve.bind(this), + ); + this.owner.on(Converter.EVENT_RESOLVE, this.onResolve.bind(this)); + this.owner.on(Converter.EVENT_END, () => { + this._excludeKinds = undefined; }); } @@ -210,9 +237,8 @@ export class CommentPlugin extends ConverterComponent { if ( reflection.kindOf( - ReflectionKind.Module | ReflectionKind.Namespace, - ) || - reflection.kind === ReflectionKind.Project + ReflectionKind.Project | ReflectionKind.SomeModule, + ) ) { comment.removeTags("@module"); comment.removeModifier("@packageDocumentation"); @@ -246,6 +272,7 @@ export class CommentPlugin extends ConverterComponent { } if (tag) { reflection.comment = new Comment(tag.content); + reflection.comment.sourcePath = comment.sourcePath; removeIfPresent(comment.blockTags, tag); } } @@ -261,6 +288,8 @@ export class CommentPlugin extends ConverterComponent { * @param node The node that is currently processed if available. */ private onDeclaration(_context: Context, reflection: Reflection) { + this.cascadeModifiers(reflection); + const comment = reflection.comment; if (!comment) return; @@ -287,6 +316,11 @@ export class CommentPlugin extends ConverterComponent { * @param context The context object describing the current state the converter is in. */ private onBeginResolve(context: Context) { + if (context.project.comment) { + this.applyModifiers(context.project, context.project.comment); + this.removeExcludedTags(context.project.comment); + } + const project = context.project; const reflections = Object.values(project.reflections); @@ -348,134 +382,124 @@ export class CommentPlugin extends ConverterComponent { !/[A-Z_][A-Z0-9_]/.test(reflection.comment.label) ) { context.logger.warn( - `The label "${ - reflection.comment.label - }" for ${reflection.getFriendlyFullName()} cannot be referenced with a declaration reference. ` + - `Labels may only contain A-Z, 0-9, and _, and may not start with a number.`, + context.i18n.label_0_for_1_cannot_be_referenced( + reflection.comment.label, + reflection.getFriendlyFullName(), + ), + ); + } + + for (const group of MUTUALLY_EXCLUSIVE_MODIFIERS) { + const intersect = setIntersection( + group, + reflection.comment.modifierTags, ); + if (intersect.size > 1) { + const [a, b] = intersect; + context.logger.warn( + context.i18n.modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2( + a, + b, + reflection.getFriendlyFullName(), + ), + ); + } } mergeSeeTags(reflection.comment); movePropertyTags(reflection.comment, reflection); + + // Unlike other modifiers, this one has to wait until resolution to be removed + // as it needs to remain present so that it can be checked when `@hidden` tags are + // being processed. + if (reflection.kindOf(ReflectionKind.Class)) { + reflection.comment.removeModifier("@hideconstructor"); + } } - if (!(reflection instanceof DeclarationReflection)) { - return; + if (reflection instanceof DeclarationReflection && reflection.comment) { + let sigs: SignatureReflection[]; + if (reflection.type instanceof ReflectionType) { + sigs = reflection.type.declaration.getNonIndexSignatures(); + } else { + sigs = reflection.getNonIndexSignatures(); + } + + // For variables and properties, the symbol might own the comment but we might also + // have @param and @returns comments for an owned signature. Only do this if there is + // exactly one signature as otherwise we have no hope of doing validation right. + if (sigs.length === 1 && !sigs[0].comment) { + this.moveSignatureParamComments(sigs[0], reflection.comment); + const returnsTag = reflection.comment.getTag("@returns"); + if (returnsTag) { + sigs[0].comment = new Comment(); + sigs[0].comment.sourcePath = reflection.comment.sourcePath; + sigs[0].comment.blockTags.push(returnsTag); + reflection.comment.removeTags("@returns"); + } + } + + // Any cascaded tags will show up twice, once on this and once on our signatures + // This is completely redundant, so remove them from the wrapping function. + if (sigs.length) { + for (const mod of this.cascadedModifierTags) { + reflection.comment.modifierTags.delete(mod); + } + } } - if (reflection.type instanceof ReflectionType) { - this.moveCommentToSignatures( - reflection, - reflection.type.declaration.getNonIndexSignatures(), - ); - } else { - this.moveCommentToSignatures( - reflection, - reflection.getNonIndexSignatures(), - ); + if (reflection instanceof SignatureReflection) { + this.moveSignatureParamComments(reflection); } } - private moveCommentToSignatures( - reflection: DeclarationReflection, - signatures: SignatureReflection[], + private moveSignatureParamComments( + signature: SignatureReflection, + comment = signature.comment, ) { - if (!signatures.length) { - return; - } - - const comment = reflection.kindOf(ReflectionKind.ClassOrInterface) - ? undefined - : reflection.comment; - - for (const signature of signatures) { - const signatureHadOwnComment = !!signature.comment; - const childComment = (signature.comment ||= comment?.clone()); - if (!childComment) continue; - - signature.parameters?.forEach((parameter, index) => { - if (parameter.name === "__namedParameters") { - const commentParams = childComment.blockTags.filter( - (tag) => - tag.tag === "@param" && !tag.name?.includes("."), - ); - if ( - signature.parameters?.length === commentParams.length && - commentParams[index].name - ) { - parameter.name = commentParams[index].name!; - } - } + if (!comment) return; - const tag = childComment.getIdentifiedTag( - parameter.name, - "@param", + signature.parameters?.forEach((parameter, index) => { + if (parameter.name === "__namedParameters") { + const commentParams = comment.blockTags.filter( + (tag) => tag.tag === "@param" && !tag.name?.includes("."), ); - - if (tag) { - parameter.comment = new Comment( - Comment.cloneDisplayParts(tag.content), - ); - } - }); - - for (const parameter of signature.typeParameters || []) { - const tag = - childComment.getIdentifiedTag( - parameter.name, - "@typeParam", - ) || - childComment.getIdentifiedTag( - parameter.name, - "@template", - ) || - childComment.getIdentifiedTag( - `<${parameter.name}>`, - "@param", - ); - if (tag) { - parameter.comment = new Comment( - Comment.cloneDisplayParts(tag.content), - ); + if ( + signature.parameters?.length === commentParams.length && + commentParams[index].name + ) { + parameter.name = commentParams[index].name!; } } - this.validateParamTags( - signature, - childComment, - signature.parameters || [], - signatureHadOwnComment, - ); + const tag = comment.getIdentifiedTag(parameter.name, "@param"); - childComment?.removeTags("@param"); - childComment?.removeTags("@typeParam"); - childComment?.removeTags("@template"); - } + if (tag) { + parameter.comment = new Comment( + Comment.cloneDisplayParts(tag.content), + ); + parameter.comment.sourcePath = comment.sourcePath; + } + }); - // Since this reflection has signatures, we need to remove the comment from the non-primary - // declaration location. For functions/methods/constructors, this means removing it from - // the wrapping reflection. For type aliases, classes, and interfaces, this means removing - // it from the contained signatures... if it's the same as what is on the signature. - // This is important so that in type aliases we don't end up with a comment rendered twice. - if (reflection.kindOf(ReflectionKind.SignatureContainer)) { - delete reflection.comment; - } else { - reflection.comment?.removeTags("@param"); - reflection.comment?.removeTags("@typeParam"); - reflection.comment?.removeTags("@template"); - - const parentComment = Comment.combineDisplayParts( - reflection.comment?.summary, - ); - for (const sig of signatures) { - if ( - Comment.combineDisplayParts(sig.comment?.summary) === - parentComment - ) { - delete sig.comment; - } + for (const parameter of signature.typeParameters || []) { + const tag = + comment.getIdentifiedTag(parameter.name, "@typeParam") || + comment.getIdentifiedTag(parameter.name, "@template") || + comment.getIdentifiedTag(`<${parameter.name}>`, "@param"); + if (tag) { + parameter.comment = new Comment( + Comment.cloneDisplayParts(tag.content), + ); + parameter.comment.sourcePath = comment.sourcePath; } } + + this.validateParamTags(signature, comment, signature.parameters || []); + + comment.removeTags("@param"); + comment.removeTags("@typeParam"); + comment.removeTags("@template"); } private removeExcludedTags(comment: Comment) { @@ -489,6 +513,29 @@ export class CommentPlugin extends ConverterComponent { } } + private cascadeModifiers(reflection: Reflection) { + const parentComment = reflection.parent?.comment; + if (!parentComment) return; + + const childMods = reflection.comment?.modifierTags ?? new Set(); + + for (const mod of this.cascadedModifierTags) { + if (parentComment.hasModifier(mod)) { + const exclusiveSet = MUTUALLY_EXCLUSIVE_MODIFIERS.find((tags) => + tags.has(mod), + ); + + if ( + !exclusiveSet || + Array.from(exclusiveSet).every((tag) => !childMods.has(tag)) + ) { + reflection.comment ||= new Comment(); + reflection.comment.modifierTags.add(mod); + } + } + } + } + /** * Determines whether or not a reflection has been hidden * @@ -515,6 +562,23 @@ export class CommentPlugin extends ConverterComponent { return true; } + if ( + reflection.kindOf( + ReflectionKind.ConstructorSignature | + ReflectionKind.Constructor, + ) + ) { + if (comment?.hasModifier("@hideconstructor")) return true; + const cls = reflection.parent?.kindOf(ReflectionKind.Class) + ? reflection.parent + : reflection.parent?.parent?.kindOf(ReflectionKind.Class) + ? reflection.parent.parent + : undefined; + if (cls?.comment?.hasModifier("@hideconstructor")) { + return true; + } + } + if (!comment) { // We haven't moved comments from the parent for signatures without a direct // comment, so don't exclude those due to not being documented. @@ -607,7 +671,6 @@ export class CommentPlugin extends ConverterComponent { signature: SignatureReflection, comment: Comment, params: ParameterReflection[], - signatureHadOwnComment: boolean, ) { const paramTags = comment.blockTags.filter( (tag) => tag.tag === "@param", @@ -617,14 +680,15 @@ export class CommentPlugin extends ConverterComponent { params.some((param) => param.name === tag.name), ); - moveNestedParamTags(/* in-out */ paramTags, params); + moveNestedParamTags(/* in-out */ paramTags, params, comment.sourcePath); - if (signatureHadOwnComment && paramTags.length) { + if (!comment.inheritedFromParentDeclaration) { for (const tag of paramTags) { this.application.logger.warn( - `The signature ${signature.getFriendlyFullName()} has an @param with name "${ - tag.name - }", which was not used.`, + this.application.i18n.signature_0_has_unused_param_with_name_1( + signature.getFriendlyFullName(), + tag.name ?? "(missing)", + ), ); } } @@ -645,14 +709,15 @@ function inTypeLiteral(refl: Reflection | undefined) { function moveNestedParamTags( /* in-out */ paramTags: CommentTag[], parameters: ParameterReflection[], + sourcePath: string | undefined, ) { const used = new Set(); for (const param of parameters) { const visitor: Partial = { reflection(target) { - const tags = paramTags.filter( - (t) => t.name?.startsWith(`${param.name}.`), + const tags = paramTags.filter((t) => + t.name?.startsWith(`${param.name}.`), ); for (const tag of tags) { @@ -664,6 +729,7 @@ function moveNestedParamTags( child.comment = new Comment( Comment.cloneDisplayParts(tag.content), ); + child.comment.sourcePath = sourcePath; used.add(paramTags.indexOf(tag)); } } @@ -704,12 +770,14 @@ function movePropertyTags(comment: Comment, container: Reflection) { child.comment = new Comment( Comment.cloneDisplayParts(prop.content), ); + child.comment.sourcePath = comment.sourcePath; if (child instanceof DeclarationReflection && child.signatures) { for (const sig of child.signatures) { sig.comment = new Comment( Comment.cloneDisplayParts(prop.content), ); + sig.comment.sourcePath = comment.sourcePath; } } } diff --git a/src/lib/converter/plugins/GroupPlugin.ts b/src/lib/converter/plugins/GroupPlugin.ts index b1e997987..eb30a54be 100644 --- a/src/lib/converter/plugins/GroupPlugin.ts +++ b/src/lib/converter/plugins/GroupPlugin.ts @@ -1,7 +1,8 @@ import { ReflectionKind, ContainerReflection, - DeclarationReflection, + type DeclarationReflection, + type DocumentReflection, } from "../../models/reflections/index"; import { ReflectionGroup } from "../../models/ReflectionGroup"; import { Component, ConverterComponent } from "../components"; @@ -11,6 +12,29 @@ import { getSortFunction } from "../../utils/sort"; import { Option, removeIf } from "../../utils"; import { Comment } from "../../models"; +// Same as the defaultKindSortOrder in sort.ts +const defaultGroupOrder = [ + ReflectionKind.Document, + ReflectionKind.Reference, + // project is never a child so never added to a group + ReflectionKind.Module, + ReflectionKind.Namespace, + ReflectionKind.Enum, + ReflectionKind.EnumMember, + ReflectionKind.Class, + ReflectionKind.Interface, + ReflectionKind.TypeAlias, + + ReflectionKind.Constructor, + ReflectionKind.Property, + ReflectionKind.Variable, + ReflectionKind.Function, + ReflectionKind.Accessor, + ReflectionKind.Method, + + // others are never added to groups +]; + /** * A handler that sorts and groups the found reflections in the resolving phase. * @@ -18,7 +42,9 @@ import { Comment } from "../../models"; */ @Component({ name: "group" }) export class GroupPlugin extends ConverterComponent { - sortFunction!: (reflections: DeclarationReflection[]) => void; + sortFunction!: ( + reflections: Array, + ) => void; @Option("searchGroupBoosts") accessor boosts!: Record; @@ -37,18 +63,24 @@ export class GroupPlugin extends ConverterComponent { * Create a new GroupPlugin instance. */ override initialize() { - this.listenTo( - this.owner, - { - [Converter.EVENT_RESOLVE_BEGIN]: () => { - this.sortFunction = getSortFunction( - this.application.options, + this.owner.on( + Converter.EVENT_RESOLVE_BEGIN, + () => { + this.sortFunction = getSortFunction(this.application.options); + GroupPlugin.WEIGHTS = this.groupOrder; + if (GroupPlugin.WEIGHTS.length === 0) { + GroupPlugin.WEIGHTS = defaultGroupOrder.map((kind) => + this.application.internationalization.kindPluralString( + kind, + ), ); - GroupPlugin.WEIGHTS = this.groupOrder; - }, - [Converter.EVENT_RESOLVE_END]: this.onEndResolve, + } }, - undefined, + -100, + ); + this.owner.on( + Converter.EVENT_RESOLVE_END, + this.onEndResolve.bind(this), -100, ); } @@ -79,31 +111,34 @@ export class GroupPlugin extends ConverterComponent { this.application.options.isSet("searchGroupBoosts") ) { context.logger.warn( - `Not all groups specified in searchGroupBoosts were used in the documentation.` + - ` The unused groups were:\n\t${Array.from( - unusedBoosts, - ).join("\n\t")}`, + context.i18n.not_all_search_group_boosts_used_0( + Array.from(unusedBoosts).join("\n\t"), + ), ); } } private group(reflection: ContainerReflection) { - if ( - reflection.children && - reflection.children.length > 0 && - !reflection.groups - ) { - if ( - this.sortEntryPoints || - !reflection.children.some((c) => - c.kindOf(ReflectionKind.Module), - ) - ) { - this.sortFunction(reflection.children); + 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); + } + } else if (reflection.documents) { + this.sortFunction(reflection.documents); + this.sortFunction(reflection.childrenIncludingDocuments); } + reflection.groups = this.getReflectionGroups( reflection, - reflection.children, + reflection.childrenIncludingDocuments, ); } } @@ -114,7 +149,7 @@ export class GroupPlugin extends ConverterComponent { * @privateRemarks * If you change this, also update extractCategories in CategoryPlugin accordingly. */ - getGroups(reflection: DeclarationReflection) { + getGroups(reflection: DeclarationReflection | DocumentReflection) { const groups = new Set(); function extractGroupTags(comment: Comment | undefined) { if (!comment) return; @@ -128,21 +163,32 @@ export class GroupPlugin extends ConverterComponent { }); } - extractGroupTags(reflection.comment); - for (const sig of reflection.getNonIndexSignatures()) { - extractGroupTags(sig.comment); - } - - if (reflection.type?.type === "reflection") { - extractGroupTags(reflection.type.declaration.comment); - for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + if (reflection.isDeclaration()) { + extractGroupTags(reflection.comment); + for (const sig of reflection.getNonIndexSignatures()) { extractGroupTags(sig.comment); } + + if (reflection.type?.type === "reflection") { + extractGroupTags(reflection.type.declaration.comment); + for (const sig of reflection.type.declaration.getNonIndexSignatures()) { + extractGroupTags(sig.comment); + } + } + } + + if (reflection.isDocument() && "group" in reflection.frontmatter) { + groups.add(String(reflection.frontmatter["group"])); + delete reflection.frontmatter["group"]; } groups.delete(""); if (groups.size === 0) { - groups.add(ReflectionKind.pluralString(reflection.kind)); + groups.add( + this.application.internationalization.kindPluralString( + reflection.kind, + ), + ); } for (const group of groups) { @@ -166,7 +212,7 @@ export class GroupPlugin extends ConverterComponent { */ getReflectionGroups( parent: ContainerReflection, - reflections: DeclarationReflection[], + reflections: Array, ): ReflectionGroup[] { const groups = new Map(); @@ -193,7 +239,10 @@ export class GroupPlugin extends ConverterComponent { cat.description = body; } else { this.application.logger.warn( - `Comment for ${parent.getFriendlyFullName()} includes @groupDescription for "${header}", but no child is placed in that group.`, + this.application.i18n.comment_for_0_includes_groupDescription_for_1_but_no_child_in_group( + parent.getFriendlyFullName(), + header, + ), ); } diff --git a/src/lib/converter/plugins/ImplementsPlugin.ts b/src/lib/converter/plugins/ImplementsPlugin.ts index 84a37083f..d71f1cb6b 100644 --- a/src/lib/converter/plugins/ImplementsPlugin.ts +++ b/src/lib/converter/plugins/ImplementsPlugin.ts @@ -1,19 +1,21 @@ import ts from "typescript"; import { ApplicationEvents } from "../../application-events"; import { - ContainerReflection, + type ContainerReflection, DeclarationReflection, - ProjectReflection, - Reflection, + type ProjectReflection, + type Reflection, + ReflectionFlag, ReflectionKind, SignatureReflection, } from "../../models/reflections/index"; -import { ReferenceType, ReflectionType, Type } from "../../models/types"; +import { ReferenceType, ReflectionType, type Type } from "../../models/types"; import { filterMap, zip } from "../../utils/array"; import { Component, ConverterComponent } from "../components"; import type { Context } from "../context"; import { Converter } from "../converter"; import { getHumanName } from "../../utils"; +import type { TranslatedString } from "../../internationalization/internationalization"; /** * A plugin that detects interface implementations of functions and @@ -28,24 +30,21 @@ export class ImplementsPlugin extends ConverterComponent { * Create a new ImplementsPlugin instance. */ override initialize() { - this.listenTo( - this.owner, + this.owner.on( Converter.EVENT_RESOLVE_END, - this.onResolveEnd, + this.onResolveEnd.bind(this), ); - this.listenTo( - this.owner, + this.owner.on( Converter.EVENT_CREATE_DECLARATION, - this.onDeclaration, + this.onDeclaration.bind(this), -1000, ); - this.listenTo( - this.owner, + this.owner.on( Converter.EVENT_CREATE_SIGNATURE, - this.onSignature, + this.onSignature.bind(this), 1000, ); - this.listenTo(this.application, ApplicationEvents.REVIVE, this.resolve); + this.application.on(ApplicationEvents.REVIVE, this.resolve.bind(this)); } /** @@ -315,10 +314,9 @@ export class ImplementsPlugin extends ConverterComponent { context.logger.warn( `Failed to retrieve${ reflection.flags.isStatic ? " static" : "" - } member "${ - reflection.escapedName ?? reflection.name - }" of "${reflection.parent - ?.name}" for inheritance analysis. Please report a bug.`, + } member "${reflection.escapedName ?? reflection.name}" of "${ + reflection.parent?.name + }" for inheritance analysis. Please report a bug.` as TranslatedString, ); return; } @@ -405,7 +403,7 @@ function createLink( clause: ts.HeritageClause, expr: ts.ExpressionWithTypeArguments, symbol: ts.Symbol, - isOverwrite: boolean, + isInherit: boolean, ) { const project = context.project; const name = `${expr.expression.getText()}.${getHumanName(symbol.name)}`; @@ -413,7 +411,9 @@ function createLink( link(reflection); link(reflection.getSignature); link(reflection.setSignature); - link(reflection.indexSignature); + for (const sig of reflection.indexSignatures || []) { + link(sig); + } for (const sig of reflection.signatures ?? []) { link(sig); } @@ -433,7 +433,8 @@ function createLink( return; } - if (isOverwrite) { + if (isInherit) { + target.setFlag(ReflectionFlag.Inherited); target.inheritedFrom ??= ReferenceType.createBrokenReference( name, project, diff --git a/src/lib/converter/plugins/InheritDocPlugin.ts b/src/lib/converter/plugins/InheritDocPlugin.ts index abb7bf606..dff49af6f 100644 --- a/src/lib/converter/plugins/InheritDocPlugin.ts +++ b/src/lib/converter/plugins/InheritDocPlugin.ts @@ -1,7 +1,7 @@ import { Comment, DeclarationReflection, - ProjectReflection, + type ProjectReflection, ReflectionKind, ReflectionType, SignatureReflection, @@ -10,7 +10,7 @@ import { Component, ConverterComponent } from "../components"; import { Converter } from "../converter"; import type { Context } from "../context"; import type { Reflection } from "../../models/reflections/abstract"; -import { Option, DefaultMap, ValidationOptions } from "../../utils"; +import { Option, DefaultMap, type ValidationOptions } from "../../utils"; import { zip } from "../../utils/array"; import { parseDeclarationReference } from "../comments/declarationReference"; import { resolveDeclarationReference } from "../comments/declarationReferenceResolver"; @@ -46,8 +46,7 @@ export class InheritDocPlugin extends ConverterComponent { ); this.application.on( ApplicationEvents.REVIVE, - this.processInheritDoc, - this, + this.processInheritDoc.bind(this), ); } @@ -65,7 +64,9 @@ export class InheritDocPlugin extends ConverterComponent { const declRef = parseDeclarationReference(source, 0, source.length); if (!declRef || /\S/.test(source.substring(declRef[1]))) { this.application.logger.warn( - `Declaration reference in @inheritDoc for ${reflection.getFriendlyFullName()} was not fully parsed and may resolve incorrectly.`, + this.application.i18n.declaration_reference_in_inheritdoc_for_0_not_fully_parsed( + reflection.getFriendlyFullName(), + ), ); } let sourceRefl = @@ -99,7 +100,10 @@ export class InheritDocPlugin extends ConverterComponent { if (!sourceRefl) { if (this.validation.invalidLink) { this.application.logger.warn( - `Failed to find "${source}" to inherit the comment from in the comment for ${reflection.getFullName()}`, + this.application.i18n.failed_to_find_0_to_inherit_comment_from_in_1( + source, + reflection.getFriendlyFullName(), + ), ); } continue; @@ -134,7 +138,10 @@ export class InheritDocPlugin extends ConverterComponent { if (!source.comment) { this.application.logger.warn( - `${target.getFullName()} tried to copy a comment from ${source.getFullName()} with @inheritDoc, but the source has no associated comment.`, + this.application.i18n.reflection_0_tried_to_copy_comment_from_1_but_source_had_no_comment( + target.getFullName(), + source.getFullName(), + ), ); return; } @@ -196,9 +203,9 @@ export class InheritDocPlugin extends ConverterComponent { parts.push(orig.name); this.application.logger.warn( - `@inheritDoc specifies a circular inheritance chain: ${parts - .reverse() - .join(" -> ")}`, + this.application.i18n.inheritdoc_circular_inheritance_chain_0( + parts.reverse().join(" -> "), + ), ); }; @@ -216,6 +223,7 @@ function copySummaries( ) { for (const [s, t] of zip(source || [], target || [])) { t.comment = new Comment(s.comment?.summary); + t.comment.sourcePath = s.comment?.sourcePath; } } diff --git a/src/lib/converter/plugins/LinkResolverPlugin.ts b/src/lib/converter/plugins/LinkResolverPlugin.ts index 85ba48b88..dcc0c840a 100644 --- a/src/lib/converter/plugins/LinkResolverPlugin.ts +++ b/src/lib/converter/plugins/LinkResolverPlugin.ts @@ -1,13 +1,14 @@ import { Component, ConverterComponent } from "../components"; -import type { Context, ExternalResolveResult } from "../../converter"; +import type { Context } from "../../converter"; import { ConverterEvents } from "../converter-events"; -import { Option, ValidationOptions } from "../../utils"; +import { Option, type ValidationOptions } from "../../utils"; import { ContainerReflection, DeclarationReflection, - ProjectReflection, - Reflection, - ReflectionCategory, + makeRecursiveVisitor, + type ProjectReflection, + type Reflection, + type ReflectionCategory, } from "../../models"; import { discoverAllReferenceTypes } from "../../utils/reflections"; import { ApplicationEvents } from "../../application-events"; @@ -22,11 +23,14 @@ export class LinkResolverPlugin extends ConverterComponent { override initialize() { super.initialize(); - this.owner.on(ConverterEvents.RESOLVE_END, this.onResolve, this, -300); + this.owner.on( + ConverterEvents.RESOLVE_END, + this.onResolve.bind(this), + -300, + ); this.application.on( ApplicationEvents.REVIVE, - this.resolveLinks, - this, + this.resolveLinks.bind(this), -300, ); } @@ -42,6 +46,19 @@ export class LinkResolverPlugin extends ConverterComponent { 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 instanceof DeclarationReflection && reflection.readme @@ -95,12 +112,10 @@ export class LinkResolverPlugin extends ConverterComponent { ); switch (typeof resolveResult) { case "string": - type.externalUrl = resolveResult as string; + type.externalUrl = resolveResult; break; case "object": - type.externalUrl = ( - resolveResult as ExternalResolveResult - ).target; + type.externalUrl = resolveResult.target; break; } } diff --git a/src/lib/converter/plugins/PackagePlugin.ts b/src/lib/converter/plugins/PackagePlugin.ts index 2032a92b7..585dd14ca 100644 --- a/src/lib/converter/plugins/PackagePlugin.ts +++ b/src/lib/converter/plugins/PackagePlugin.ts @@ -24,9 +24,6 @@ export class PackagePlugin extends ConverterComponent { @Option("readme") accessor readme!: string; - @Option("stripYamlFrontmatter") - accessor stripYamlFrontmatter!: boolean; - @Option("entryPointStrategy") accessor entryPointStrategy!: EntryPointStrategy; @@ -52,18 +49,17 @@ export class PackagePlugin extends ConverterComponent { private packageJson?: { name: string; version?: string }; override initialize() { - this.listenTo(this.owner, { - [Converter.EVENT_BEGIN]: this.onBegin, - [Converter.EVENT_RESOLVE_BEGIN]: this.onBeginResolve, - [Converter.EVENT_END]: () => { - delete this.readmeFile; - delete this.readmeContents; - delete this.packageJson; - }, - }); - this.listenTo(this.application, { - [ApplicationEvents.REVIVE]: this.onRevive, + this.owner.on(Converter.EVENT_BEGIN, this.onBegin.bind(this)); + this.owner.on( + Converter.EVENT_RESOLVE_BEGIN, + this.onBeginResolve.bind(this), + ); + this.owner.on(Converter.EVENT_END, () => { + delete this.readmeFile; + delete this.readmeContents; + delete this.packageJson; }); + this.application.on(ApplicationEvents.REVIVE, this.onRevive.bind(this)); } private onRevive(project: ProjectReflection) { @@ -102,15 +98,13 @@ export class PackagePlugin extends ConverterComponent { if (this.readme) { // Readme path provided, read only that file. try { - this.readmeContents = this.processReadmeContents( - readFile(this.readme), - ); + this.readmeContents = readFile(this.readme); this.readmeFile = this.readme; } catch { this.application.logger.error( - `Provided README path, ${nicePath( - this.readme, - )} could not be read.`, + this.application.i18n.provided_readme_at_0_could_not_be_read( + nicePath(this.readme), + ), ); } } else { @@ -123,46 +117,23 @@ export class PackagePlugin extends ConverterComponent { if (result) { this.readmeFile = result.file; - this.readmeContents = this.processReadmeContents( - result.content, - ); + this.readmeContents = result.content; } } } - private processReadmeContents(contents: string) { - if (this.stripYamlFrontmatter) { - return contents.replace( - /^\s*---\r?\n[\s\S]*?\r?\n---\s*?\r?\n\s*/, - "", - ); - } - return contents; - } - private onBeginResolve(context: Context) { this.addEntries(context.project); } private addEntries(project: ProjectReflection) { if (this.readmeFile && this.readmeContents) { - const comment = this.application.converter.parseRawComment( + const { content } = this.application.converter.parseRawComment( new MinimalSourceFile(this.readmeContents, this.readmeFile), + project.files, ); - if (comment.blockTags.length || comment.modifierTags.size) { - const ignored = [ - ...comment.blockTags.map((tag) => tag.tag), - ...comment.modifierTags, - ]; - this.application.logger.warn( - `Block and modifier tags will be ignored within the readme:\n\t${ignored.join( - "\n\t", - )}`, - ); - } - - project.readme = comment.summary; + project.readme = content; } if (this.packageJson) { @@ -178,7 +149,7 @@ export class PackagePlugin extends ConverterComponent { } } else if (!project.name) { this.application.logger.warn( - 'The --name option was not specified, and no package.json was found. Defaulting project name to "Documentation".', + this.application.i18n.defaulting_project_name(), ); project.name = "Documentation"; } diff --git a/src/lib/converter/plugins/SourcePlugin.ts b/src/lib/converter/plugins/SourcePlugin.ts index 03df0d54e..7679415df 100644 --- a/src/lib/converter/plugins/SourcePlugin.ts +++ b/src/lib/converter/plugins/SourcePlugin.ts @@ -9,15 +9,9 @@ import { Converter } from "../converter"; import type { Context } from "../context"; import { Option, normalizePath, getCommonDirectory } from "../../utils"; import { isNamedNode } from "../utils/nodes"; -import { dirname, relative } from "path"; +import { relative } from "path"; import { SourceReference } from "../../models"; -import { - AssumedRepository, - gitIsInstalled, - GitRepository, - Repository, -} from "../utils/repository"; -import { BasePath } from "../utils/base-path"; +import { gitIsInstalled, RepositoryManager } from "../utils/repository"; /** * A handler that attaches source file information to reflections. @@ -47,26 +41,25 @@ export class SourcePlugin extends ConverterComponent { */ private fileNames = new Set(); - /** - * List of known repositories. - */ - private repositories: { [path: string]: Repository } = {}; - - /** - * List of paths known to be not under git control. - */ - private ignoredPaths = new Set(); + private repositories?: RepositoryManager; /** * Create a new SourceHandler instance. */ override initialize() { - this.listenTo(this.owner, { - [Converter.EVENT_END]: this.onEnd, - [Converter.EVENT_CREATE_DECLARATION]: this.onDeclaration, - [Converter.EVENT_CREATE_SIGNATURE]: this.onSignature, - [Converter.EVENT_RESOLVE_BEGIN]: this.onBeginResolve, - }); + this.owner.on(Converter.EVENT_END, this.onEnd.bind(this)); + this.owner.on( + Converter.EVENT_CREATE_DECLARATION, + this.onDeclaration.bind(this), + ); + this.owner.on( + Converter.EVENT_CREATE_SIGNATURE, + this.onSignature.bind(this), + ); + this.owner.on( + Converter.EVENT_RESOLVE_BEGIN, + this.onBeginResolve.bind(this), + ); } private onEnd() { @@ -91,7 +84,7 @@ export class SourcePlugin extends ConverterComponent { const symbol = reflection.project.getSymbolFromReflection(reflection); for (const node of symbol?.declarations || []) { const sourceFile = node.getSourceFile(); - const fileName = BasePath.normalize(sourceFile.fileName); + const fileName = normalizePath(sourceFile.fileName); this.fileNames.add(fileName); let position: ts.LineAndCharacter; @@ -126,7 +119,7 @@ export class SourcePlugin extends ConverterComponent { if (this.disableSources || !sig) return; const sourceFile = sig.getSourceFile(); - const fileName = BasePath.normalize(sourceFile.fileName); + const fileName = normalizePath(sourceFile.fileName); this.fileNames.add(fileName); const position = ts.getLineAndCharacterOfPosition( @@ -154,7 +147,7 @@ export class SourcePlugin extends ConverterComponent { if (this.disableGit && !this.sourceLinkTemplate) { this.application.logger.error( - `disableGit is set, but sourceLinkTemplate is not, so source links cannot be produced. Set a sourceLinkTemplate or disableSources to prevent source tracking.`, + context.i18n.disable_git_set_but_not_source_link_template(), ); return; } @@ -164,12 +157,20 @@ export class SourcePlugin extends ConverterComponent { !this.gitRevision ) { this.application.logger.warn( - `disableGit is set and sourceLinkTemplate contains {gitRevision}, which will be replaced with an empty string as no revision was provided.`, + context.i18n.disable_git_set_and_git_revision_used(), ); } const basePath = this.basePath || getCommonDirectory([...this.fileNames]); + this.repositories ||= new RepositoryManager( + basePath, + this.gitRevision, + this.gitRemote, + this.sourceLinkTemplate, + this.disableGit, + this.application.logger, + ); for (const id in context.project.reflections) { const refl = context.project.reflections[id]; @@ -189,8 +190,7 @@ export class SourcePlugin extends ConverterComponent { for (const source of refl.sources || []) { if (this.disableGit || gitIsInstalled()) { - const repo = this.getRepository( - basePath, + const repo = this.repositories.getRepository( source.fullFileName, ); source.url = repo?.getURL(source.fullFileName, source.line); @@ -202,57 +202,6 @@ export class SourcePlugin extends ConverterComponent { } } } - - /** - * Check whether the given file is placed inside a repository. - * - * @param fileName The name of the file a repository should be looked for. - * @returns The found repository info or undefined. - */ - private getRepository( - basePath: string, - fileName: string, - ): Repository | undefined { - if (this.disableGit) { - return new AssumedRepository( - basePath, - this.gitRevision, - this.sourceLinkTemplate, - ); - } - - // Check for known non-repositories - const dirName = dirname(fileName); - const segments = dirName.split("/"); - for (let i = segments.length; i > 0; i--) { - if (this.ignoredPaths.has(segments.slice(0, i).join("/"))) { - return; - } - } - - // Check for known repositories - for (const path of Object.keys(this.repositories)) { - if (fileName.toLowerCase().startsWith(path)) { - return this.repositories[path]; - } - } - - // Try to create a new repository - const repository = GitRepository.tryCreateRepository( - dirName, - this.sourceLinkTemplate, - this.gitRevision, - this.gitRemote, - this.application.logger, - ); - if (repository) { - this.repositories[repository.path.toLowerCase()] = repository; - return repository; - } - - // No repository found, add path to ignored paths - this.ignoredPaths.add(dirName); - } } function getLocationNode(node: ts.Node) { diff --git a/src/lib/converter/plugins/TypePlugin.ts b/src/lib/converter/plugins/TypePlugin.ts index 13c946978..fcf83ace0 100644 --- a/src/lib/converter/plugins/TypePlugin.ts +++ b/src/lib/converter/plugins/TypePlugin.ts @@ -1,11 +1,11 @@ import { ReflectionKind, DeclarationReflection, - DeclarationHierarchy, - ProjectReflection, - Reflection, + type DeclarationHierarchy, + type ProjectReflection, + type Reflection, } from "../../models/reflections/index"; -import { Type, ReferenceType } from "../../models/types"; +import { type Type, ReferenceType } from "../../models/types"; import { Component, ConverterComponent } from "../components"; import { Converter } from "../converter"; import type { Context } from "../context"; @@ -22,14 +22,13 @@ export class TypePlugin extends ConverterComponent { * Create a new TypeHandler instance. */ override initialize() { - this.listenTo(this.owner, { - [Converter.EVENT_RESOLVE]: this.onResolve, - [Converter.EVENT_RESOLVE_END]: this.onResolveEnd, - [Converter.EVENT_END]: () => this.reflections.clear(), - }); - this.listenTo(this.application, { - [ApplicationEvents.REVIVE]: this.onRevive, - }); + this.owner.on(Converter.EVENT_RESOLVE, this.onResolve.bind(this)); + this.owner.on( + Converter.EVENT_RESOLVE_END, + this.onResolveEnd.bind(this), + ); + this.owner.on(Converter.EVENT_END, () => this.reflections.clear()); + this.application.on(ApplicationEvents.REVIVE, this.onRevive.bind(this)); } private onRevive(project: ProjectReflection) { @@ -44,7 +43,7 @@ export class TypePlugin extends ConverterComponent { this.reflections.clear(); } - private onResolve(context: Context, reflection: DeclarationReflection) { + private onResolve(context: Context, reflection: Reflection) { this.resolve(context.project, reflection); } @@ -129,8 +128,8 @@ export class TypePlugin extends ConverterComponent { }); } - let root!: DeclarationHierarchy; - let hierarchy!: DeclarationHierarchy; + let root: DeclarationHierarchy | undefined; + let hierarchy: DeclarationHierarchy | undefined; function push(types: Type[]) { const level: DeclarationHierarchy = { types: types }; if (hierarchy) { @@ -152,14 +151,14 @@ export class TypePlugin extends ConverterComponent { project, ), ]); - hierarchy.isTarget = true; + hierarchy!.isTarget = true; if (reflection.extendedBy) { push(reflection.extendedBy); } // No point setting up a hierarchy if there is no hierarchy to display - if (root.next) { + if (root!.next) { reflection.typeHierarchy = root; } }); diff --git a/src/lib/converter/symbols.ts b/src/lib/converter/symbols.ts index d94e586a1..691e21832 100644 --- a/src/lib/converter/symbols.ts +++ b/src/lib/converter/symbols.ts @@ -5,10 +5,10 @@ import { IntrinsicType, LiteralType, ReferenceReflection, - Reflection, + type Reflection, ReflectionFlag, ReflectionKind, - ConversionFlags, + type UnionType, } from "../models"; import { getEnumFlags, @@ -18,7 +18,7 @@ import { } from "../utils/enum"; import type { Context } from "./context"; import { convertDefaultValue } from "./convert-expression"; -import { convertIndexSignature } from "./factories/index-signature"; +import { convertIndexSignatures } from "./factories/index-signature"; import { createConstructSignatureWithType, createSignature, @@ -33,7 +33,7 @@ const symbolConverters: { context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, - ) => void | ts.SymbolFlags; + ) => undefined | ts.SymbolFlags; } = { [ts.SymbolFlags.RegularEnum]: convertEnum, [ts.SymbolFlags.ConstEnum]: convertEnum, @@ -210,7 +210,7 @@ function convertEnum( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.Enum, symbol, @@ -235,7 +235,7 @@ function convertEnumMember( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.EnumMember, symbol, @@ -261,7 +261,7 @@ function convertNamespace( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { let exportFlags = ts.SymbolFlags.ModuleMember; // This can happen in JS land where "class" functions get tagged as a namespace too @@ -307,9 +307,9 @@ function convertTypeAlias( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { const declaration = symbol - ?.getDeclarations() + .getDeclarations() ?.find( ( d, @@ -350,6 +350,10 @@ function convertTypeAlias( declaration.type, ); + if (reflection.type.type === "union") { + attachUnionComments(context, declaration, reflection.type); + } + context.finalizeDeclarationReflection(reflection); // Do this after finalization so that the CommentPlugin can get @typeParam tags @@ -367,12 +371,46 @@ function convertTypeAlias( } } +function attachUnionComments( + context: Context, + declaration: ts.TypeAliasDeclaration, + union: UnionType, +) { + const list = declaration.type.getChildAt(0); + if (list.kind !== ts.SyntaxKind.SyntaxList) return; + + let unionIndex = 0; + for (const child of list.getChildren()) { + const comment = context.getNodeComment(child, false); + if (comment?.modifierTags.size || comment?.blockTags.length) { + context.logger.warn( + context.logger.i18n.comment_for_0_should_not_contain_block_or_modifier_tags( + `${context.scope.getFriendlyFullName()}.${unionIndex}`, + ), + child, + ); + } + + if (comment) { + union.elementSummaries ||= Array.from( + { length: union.types.length }, + () => [], + ); + union.elementSummaries[unionIndex] = comment.summary; + } + + if (child.kind !== ts.SyntaxKind.BarToken) { + ++unionIndex; + } + } +} + function convertTypeAliasAsInterface( context: Context, symbol: ts.Symbol, exportSymbol: ts.Symbol | undefined, declaration: ts.TypeAliasDeclaration, -) { +): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.Interface, symbol, @@ -385,7 +423,7 @@ function convertTypeAliasAsInterface( if (type.getFlags() & ts.TypeFlags.Union) { context.logger.warn( - `Using @interface on a union type will discard properties not present on all branches of the union. TypeDoc's output may not accurately describe your source code.`, + context.i18n.converting_union_as_interface(), declaration, ); } @@ -413,14 +451,14 @@ function convertTypeAliasAsInterface( convertConstructSignatures(rc, symbol); // And finally, index signatures - convertIndexSignature(rc, symbol); + convertIndexSignatures(rc, symbol); } function convertFunctionOrMethod( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined | ts.SymbolFlags { // 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 = !!( @@ -486,8 +524,6 @@ function convertFunctionOrMethod( const scope = context.withScope(reflection); - // Can't use zip here. We might have less declarations than signatures - // or less signatures than declarations. for (const sig of signatures) { createSignature(scope, ReflectionKind.CallSignature, sig, symbol); } @@ -572,7 +608,7 @@ function convertClassOrInterface( const constructMember = reflectionContext.createDeclarationReflection( ReflectionKind.Constructor, - ctors?.[0]?.declaration?.symbol, + ctors[0]?.declaration?.symbol, void 0, "constructor", ); @@ -605,7 +641,7 @@ function convertClassOrInterface( // And type arguments if (instanceType.typeParameters) { reflection.typeParameters = instanceType.typeParameters.map((param) => { - const declaration = param.symbol?.declarations?.[0]; + const declaration = param.symbol.declarations?.[0]; assert(declaration && ts.isTypeParameterDeclaration(declaration)); return createTypeParamReflection(declaration, reflectionContext); }); @@ -630,7 +666,7 @@ function convertClassOrInterface( convertConstructSignatures(reflectionContext, symbol); // And finally, index signatures - convertIndexSignature(reflectionContext, symbol); + convertIndexSignatures(reflectionContext, symbol); // Normally this shouldn't matter, unless someone did something with skipLibCheck on. return ts.SymbolFlags.Alias; @@ -640,7 +676,7 @@ function convertProperty( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined | ts.SymbolFlags { // This might happen if we're converting a function-module created with Object.assign // or `export default () => {}` if (context.scope.kindOf(ReflectionKind.VariableContainer)) { @@ -699,7 +735,6 @@ function convertProperty( symbol, exportSymbol, ); - reflection.conversionFlags |= ConversionFlags.VariableOrPropertySource; const declaration = symbol.getDeclarations()?.[0]; let parameterType: ts.TypeNode | undefined; @@ -740,7 +775,7 @@ function convertArrowAsMethod( symbol: ts.Symbol, arrow: ts.ArrowFunction, exportSymbol?: ts.Symbol, -) { +): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.Method, symbol, @@ -779,7 +814,7 @@ function convertArrowAsMethod( ); } -function convertConstructor(context: Context, symbol: ts.Symbol) { +function convertConstructor(context: Context, symbol: ts.Symbol): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.Constructor, symbol, @@ -843,7 +878,7 @@ function convertAlias( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { const reflection = context.project.getReflectionFromSymbol( context.resolveAliasedSymbol(symbol), ); @@ -881,7 +916,7 @@ function convertVariable( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined | ts.SymbolFlags { const declaration = symbol.getDeclarations()?.[0]; const comment = context.getComment(symbol, ReflectionKind.Variable); @@ -965,7 +1000,7 @@ function convertVariableAsEnum( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined | ts.SymbolFlags { const reflection = context.createDeclarationReflection( ReflectionKind.Enum, symbol, @@ -1011,10 +1046,7 @@ function convertVariableAsNamespace( context.finalizeDeclarationReflection(reflection); const rc = context.withScope(reflection); - const declaration = symbol.declarations?.find(ts.isVariableDeclaration); - assert(declaration, "Missing variable declaration"); - const type = context.checker.getTypeAtLocation(declaration); - + const type = context.checker.getTypeOfSymbol(symbol); convertSymbols(rc, type.getProperties()); return ts.SymbolFlags.Property; @@ -1043,7 +1075,6 @@ function convertVariableAsFunction( exportSymbol, ); setModifiers(symbol, accessDeclaration, reflection); - reflection.conversionFlags |= ConversionFlags.VariableOrPropertySource; context.finalizeDeclarationReflection(reflection); const reflectionContext = context.withScope(reflection); @@ -1106,7 +1137,9 @@ function convertSymbolAsClass( if (!symbol.valueDeclaration) { context.logger.error( - `No value declaration found when converting ${symbol.name} as a class`, + context.i18n.converting_0_as_class_requires_value_declaration( + symbol.name, + ), symbol.declarations?.[0], ); return; @@ -1134,7 +1167,7 @@ function convertSymbolAsClass( if (ctors.length) { const constructMember = rc.createDeclarationReflection( ReflectionKind.Constructor, - ctors?.[0]?.declaration?.symbol, + ctors[0]?.declaration?.symbol, void 0, "constructor", ); @@ -1160,7 +1193,9 @@ function convertSymbolAsClass( } } else { context.logger.warn( - `${reflection.getFriendlyFullName()} is being converted as a class, but does not have any construct signatures`, + context.i18n.converting_0_as_class_without_construct_signatures( + reflection.getFriendlyFullName(), + ), symbol.valueDeclaration, ); } @@ -1176,7 +1211,7 @@ function convertAccessor( context: Context, symbol: ts.Symbol, exportSymbol?: ts.Symbol, -) { +): undefined { const reflection = context.createDeclarationReflection( ReflectionKind.Accessor, symbol, @@ -1237,8 +1272,8 @@ function isInherited(context: Context, symbol: ts.Symbol) { parents.push(...constructorDecls); return ( - parents.some( - (d) => symbol.getDeclarations()?.some((d2) => d2.parent === d), + parents.some((d) => + symbol.getDeclarations()?.some((d2) => d2.parent === d), ) === false ); } diff --git a/src/lib/converter/types.ts b/src/lib/converter/types.ts index 01b88154f..2f111c56c 100644 --- a/src/lib/converter/types.ts +++ b/src/lib/converter/types.ts @@ -25,13 +25,13 @@ import { OptionalType, RestType, TemplateLiteralType, - SomeType, + type SomeType, } from "../models"; import { ReflectionSymbolId } from "../models/reflections/ReflectionSymbolId"; import { zip } from "../utils/array"; import type { Context } from "./context"; import { ConverterEvents } from "./converter-events"; -import { convertIndexSignature } from "./factories/index-signature"; +import { convertIndexSignatures } from "./factories/index-signature"; import { convertParameterNodes, convertTypeParameterNodes, @@ -40,6 +40,7 @@ import { import { convertSymbol } from "./symbols"; import { isObjectType } from "./utils/nodes"; import { removeUndefined } from "./utils/reflections"; +import type { TranslatedString } from "../internationalization/internationalization"; export interface TypeConverter< TNode extends ts.TypeNode = ts.TypeNode, @@ -241,7 +242,11 @@ const constructorConverter: TypeConverter = { rc.convertingTypeNode = true; context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); const signature = new SignatureReflection( "__type", @@ -291,7 +296,11 @@ const constructorConverter: TypeConverter = { context.scope, ); context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); createSignature( context.withScope(reflection), @@ -347,7 +356,11 @@ const functionTypeConverter: TypeConverter = { const rc = context.withScope(reflection); context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); const signature = new SignatureReflection( "__type", @@ -358,7 +371,7 @@ const functionTypeConverter: TypeConverter = { signature, new ReflectionSymbolId(symbol, node), ); - context.registerReflection(signature, void 0); + context.registerReflection(signature, undefined); const signatureCtx = rc.withScope(signature); reflection.signatures = [signature]; @@ -387,7 +400,11 @@ const functionTypeConverter: TypeConverter = { context.scope, ); context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); createSignature( context.withScope(reflection), @@ -588,7 +605,11 @@ const typeLiteralConverter: TypeConverter = { rc.convertingTypeNode = true; context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); for (const prop of context.checker.getPropertiesOfType(type)) { convertSymbol(rc, prop); @@ -610,7 +631,7 @@ const typeLiteralConverter: TypeConverter = { ); } - convertIndexSignature(rc, symbol); + convertIndexSignatures(rc, symbol); return new ReflectionType(reflection); }, @@ -622,7 +643,11 @@ const typeLiteralConverter: TypeConverter = { context.scope, ); context.registerReflection(reflection, symbol); - context.trigger(ConverterEvents.CREATE_DECLARATION, reflection); + context.converter.trigger( + ConverterEvents.CREATE_DECLARATION, + context, + reflection, + ); for (const prop of context.checker.getPropertiesOfType(type)) { convertSymbol(context.withScope(reflection), prop); @@ -645,7 +670,7 @@ const typeLiteralConverter: TypeConverter = { } if (symbol) { - convertIndexSignature(context.withScope(reflection), symbol); + convertIndexSignatures(context.withScope(reflection), symbol); } return new ReflectionType(reflection); @@ -743,9 +768,17 @@ const referenceConverter: TypeConverter< return ref; } + let name; + if (ts.isIdentifier(node.typeName)) { + name = node.typeName.text; + } else { + name = node.typeName.right.text; + } + const ref = ReferenceType.createSymbolReference( context.resolveAliasedSymbol(symbol), context, + name, ); if (type.flags & ts.TypeFlags.Substitution) { // NoInfer @@ -828,7 +861,7 @@ const mappedConverter: TypeConverter< const templateType = convertType(context, type.templateType); return new MappedType( - type.typeParameter.symbol?.name, + type.typeParameter.symbol.name || "__type", convertType(context, type.typeParameter.getConstraint()), optionalModifier === "+" ? removeUndefined(templateType) @@ -1057,6 +1090,7 @@ const unionConverter: TypeConverter = { }, convertType(context, type) { const types = type.types.map((type) => convertType(context, type)); + normalizeUnion(types); sortLiteralUnion(types); return new UnionType(types); @@ -1088,14 +1122,14 @@ function requestBugReport(context: Context, nodeOrType: ts.Node | ts.Type) { if ("kind" in nodeOrType) { const kindName = ts.SyntaxKind[nodeOrType.kind]; context.logger.warn( - `Failed to convert type node with kind: ${kindName} and text ${nodeOrType.getText()}. Please report a bug.`, + `Failed to convert type node with kind: ${kindName} and text ${nodeOrType.getText()}. Please report a bug.` as TranslatedString, nodeOrType, ); return new UnknownType(nodeOrType.getText()); } else { const typeString = context.checker.typeToString(nodeOrType); context.logger.warn( - `Failed to convert type: ${typeString} when converting ${context.scope.getFullName()}. Please report a bug.`, + `Failed to convert type: ${typeString} when converting ${context.scope.getFullName()}. Please report a bug.` as TranslatedString, ); return new UnknownType(typeString); } @@ -1142,3 +1176,32 @@ function sortLiteralUnion(types: SomeType[]) { return (aLit.value as number) - (bLit.value as number); }); } + +function normalizeUnion(types: SomeType[]) { + let trueIndex = -1; + let falseIndex = -1; + for ( + let i = 0; + i < types.length && (trueIndex === -1 || falseIndex === -1); + i++ + ) { + const t = types[i]; + if (t instanceof LiteralType) { + if (t.value === true) { + trueIndex = i; + } + if (t.value === false) { + falseIndex = i; + } + } + } + + if (trueIndex !== -1 && falseIndex !== -1) { + types.splice(Math.max(trueIndex, falseIndex), 1); + types.splice( + Math.min(trueIndex, falseIndex), + 1, + new IntrinsicType("boolean"), + ); + } +} diff --git a/src/lib/converter/utils/base-path.ts b/src/lib/converter/utils/base-path.ts deleted file mode 100644 index d1bceaf8b..000000000 --- a/src/lib/converter/utils/base-path.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as Path from "path"; - -/** - * Helper class that determines the common base path of a set of files. - * - * In the first step all files must be passed to {@link add}. Afterwards {@link trim} - * can be used to retrieve the shortest path relative to the determined base path. - */ -export class BasePath { - /** - * List of known base paths. - */ - private basePaths: string[] = []; - - /** - * Add the given file path to this set of base paths. - * - * @param fileName The absolute filename that should be added to the base path. - */ - add(fileName: string) { - const fileDir = Path.dirname(BasePath.normalize(fileName)); - const filePath = fileDir.split("/"); - - basePaths: for (let n = 0, c = this.basePaths.length; n < c; n++) { - const basePath = this.basePaths[n].split("/"); - const mMax = Math.min(basePath.length, filePath.length); - for (let m = 0; m < mMax; m++) { - if (basePath[m] === filePath[m]) { - continue; - } - - if (m < 1) { - // No match at all, try next known base path - continue basePaths; - } else { - // Partial match, trim the known base path - if (m < basePath.length) { - this.basePaths[n] = basePath.slice(0, m).join("/"); - } - return; - } - } - - // Complete match, exit - this.basePaths[n] = basePath.splice(0, mMax).join("/"); - return; - } - - // Unknown base path, add it - this.basePaths.push(fileDir); - } - - /** - * Trim the given filename by the determined base paths. - * - * @param fileName The absolute filename that should be trimmed. - * @returns The trimmed version of the filename. - */ - trim(fileName: string): string { - fileName = BasePath.normalize(fileName); - for (let n = 0, c = this.basePaths.length; n < c; n++) { - const basePath = this.basePaths[n]; - if (fileName.substring(0, basePath.length) === basePath) { - return fileName.substring(basePath.length + 1); - } - } - - return fileName; - } - - /** - * Reset this instance, ignore all paths already passed to {@link add}. - */ - reset() { - this.basePaths = []; - } - - /** - * Normalize the given path. - * - * @param path The path that should be normalized. - * @returns Normalized version of the given path. - */ - static normalize(path: string): string { - // Ensure forward slashes - path = path.replace(/\\/g, "/"); - - // Remove all surrounding quotes - path = path.replace(/^["']+|["']+$/g, ""); - - // Make Windows drive letters upper case - return path.replace(/^([^:]+):\//, (_m, m1) => m1.toUpperCase() + ":/"); - } -} diff --git a/src/lib/converter/utils/reflections.ts b/src/lib/converter/utils/reflections.ts index 91f12165b..351e11428 100644 --- a/src/lib/converter/utils/reflections.ts +++ b/src/lib/converter/utils/reflections.ts @@ -1,4 +1,4 @@ -import { IntrinsicType, SomeType, UnionType } from "../../models"; +import { IntrinsicType, type SomeType, UnionType } from "../../models"; export function removeUndefined(type: SomeType): SomeType { if (type instanceof UnionType) { diff --git a/src/lib/converter/utils/repository.ts b/src/lib/converter/utils/repository.ts index 07cba5e9e..6fd9aca51 100644 --- a/src/lib/converter/utils/repository.ts +++ b/src/lib/converter/utils/repository.ts @@ -1,6 +1,8 @@ import { spawnSync } from "child_process"; -import type { Logger } from "../../utils"; -import { BasePath } from "../utils/base-path"; +import { normalizePath, type Logger } from "../../utils"; +import { NonEnumerable } from "../../utils/general"; +import { dirname, join } from "path"; +import { existsSync } from "fs"; const TEN_MEGABYTES = 1024 * 10000; @@ -12,13 +14,14 @@ function git(...args: string[]) { }); } -let haveGit: boolean; +let haveGit: boolean | undefined; export function gitIsInstalled() { haveGit ??= git("--version").status === 0; return haveGit; } export interface Repository { + readonly path: string; getURL(fileName: string, line: number): string | undefined; } @@ -56,6 +59,7 @@ export class GitRepository implements Repository { /** * All files tracked by the repository. */ + @NonEnumerable files = new Set(); urlTemplate: string; @@ -71,11 +75,11 @@ export class GitRepository implements Repository { this.gitRevision = gitRevision; this.urlTemplate = urlTemplate; - const out = git("-C", path, "ls-files"); + const out = git("-C", path, "ls-files", "-z"); if (out.status === 0) { - out.stdout.split("\n").forEach((file) => { + out.stdout.split("\0").forEach((file) => { if (file !== "") { - this.files.add(BasePath.normalize(path + "/" + file)); + this.files.add(normalizePath(path + "/" + file)); } }); } @@ -121,9 +125,6 @@ export class GitRepository implements Repository { gitRemote: string, logger: Logger, ): GitRepository | undefined { - const topLevel = git("-C", path, "rev-parse", "--show-toplevel"); - if (topLevel.status !== 0) return; - gitRevision ||= git("-C", path, "rev-parse", "HEAD").stdout.trim(); if (!gitRevision) return; // Will only happen in a repo with no commits. @@ -137,19 +138,109 @@ export class GitRepository implements Repository { remotesOut.stdout.split("\n"), ); } else { - logger.warn( - `The provided git remote "${gitRemote}" was not valid. Source links will be broken.`, - ); + logger.warn(logger.i18n.git_remote_0_not_valid(gitRemote)); } } if (!urlTemplate) return; - return new GitRepository( - BasePath.normalize(topLevel.stdout.replace("\n", "")), - gitRevision, - urlTemplate, - ); + return new GitRepository(normalizePath(path), gitRevision, urlTemplate); + } +} + +/** + * Responsible for keeping track of 0-N repositories which exist on a machine. + * This used to be inlined in SourcePlugin, moved out for easy unit testing. + * + * Git repositories can be nested. Files should be resolved to a repo as shown + * below: + * ```text + * /project + * /project/.git (A) + * /project/file.js (A) + * /project/folder/file.js (A) + * /project/sub/.git (B) + * /project/sub/file.js (B) + * ``` + * + * In order words, it is not safe to assume that just because a file is within + * the `/project` directory, that it belongs to repo `A`. As calling git is + * expensive (~20-300ms depending on the machine, antivirus, etc.) we check for + * `.git` folders manually, and only call git if one is found. + * + * Symlinked files have the potential to further complicate this. If TypeScript's + * `preserveSymlinks` option is on, then this may be passed the path to a symlinked + * file. Unlike TypeScript, we will resolve the path, as the repo link should really + * point to the actual file. + */ +export class RepositoryManager { + private cache = new Map(); + private assumedRepo = new AssumedRepository( + this.basePath, + this.gitRevision, + this.sourceLinkTemplate, + ); + + constructor( + private basePath: string, + private gitRevision: string, + private gitRemote: string, + private sourceLinkTemplate: string, + private disableGit: boolean, + private logger: Logger, + ) {} + + /** + * Check whether the given file is placed inside a repository. + * + * @param fileName The name of the file a repository should be looked for. + * @returns The found repository info or undefined. + */ + getRepository(fileName: string): Repository | undefined { + if (this.disableGit) { + return this.assumedRepo; + } + return this.getRepositoryFolder(normalizePath(dirname(fileName))); + } + + private getRepositoryFolder(dir: string): Repository | undefined { + if (this.cache.has(dir)) { + return this.cache.get(dir); + } + + if (existsSync(join(dir, ".git"))) { + // This might just be a git repo, or we might be in some self-recursive symlink + // loop, and the repo is actually somewhere else. Ask Git where the repo actually is. + const repo = git("-C", dir, "rev-parse", "--show-toplevel"); + if (repo.status === 0) { + const repoDir = repo.stdout.replace("\n", ""); + // This check is only necessary if we're in a symlink loop, otherwise + // it will always be true. + if (!this.cache.has(repoDir)) { + this.cache.set( + repoDir, + GitRepository.tryCreateRepository( + repoDir, + this.sourceLinkTemplate, + this.gitRevision, + this.gitRemote, + this.logger, + ), + ); + } + + this.cache.set(dir, this.cache.get(repoDir)); + } else { + // Not a git repo, probably corrupt. + this.cache.set(dir, undefined); + } + } else { + // We may be at the root of the file system, in which case there is no repo. + this.cache.set(dir, undefined); + this.cache.set(dir, this.getRepositoryFolder(dirname(dir))); + } + + return this.cache.get(dir); } } diff --git a/src/lib/internationalization/index.ts b/src/lib/internationalization/index.ts new file mode 100644 index 000000000..c9a6f3d4e --- /dev/null +++ b/src/lib/internationalization/index.ts @@ -0,0 +1,12 @@ +/** + * Internationalization module, see {@link "Adding Locales" | Adding Locales} for details on extending TypeDoc. + * + * @module + * @document ../../../internal-docs/internationalization.md + */ +export { + type TranslatableStrings, + Internationalization, + type TranslatedString, + type TranslationProxy, +} from "./internationalization"; diff --git a/src/lib/internationalization/internationalization.ts b/src/lib/internationalization/internationalization.ts new file mode 100644 index 000000000..9be120f9e --- /dev/null +++ b/src/lib/internationalization/internationalization.ts @@ -0,0 +1,319 @@ +import { ok } from "assert"; +import type { Application } from "../application"; +import { DefaultMap, unique } from "../utils"; +import { + translatable, + type BuiltinTranslatableStringArgs, +} from "./translatable"; +import { readdirSync } from "fs"; +import { join } from "path"; +import { ReflectionKind } from "../models/reflections/kind"; +import { ReflectionFlag } from "../models"; + +/** + * ### What is translatable? + * TypeDoc includes a lot of literal strings. By convention, messages which are displayed + * to the user at the INFO level or above should be present in this object to be available + * for translation. Messages at the VERBOSE level need not be translated as they are primarily + * intended for debugging. ERROR/WARNING deprecation messages related to TypeDoc's API, or + * requesting users submit a bug report need not be translated. + * + * Errors thrown by TypeDoc are generally *not* considered translatable as they are not + * displayed to the user. An exception to this is errors thrown by the `validate` method + * on options, as option readers will use them to report errors to the user. + * + * ### Interface Keys + * This object uses a similar convention as TypeScript, where the specified key should + * indicate where placeholders are present by including a number in the name. This is + * so that translations can easily tell that they are including the appropriate placeholders. + * This will also be validated at runtime by the {@link Internationalization} class, but + * it's better to have that hint when editing as well. + * + * This interface defines the available translatable strings, and the number of placeholders + * that are required to use each string. Plugins may use declaration merging to add members + * to this interface to use TypeDoc's internationalization module. + * + * @example + * ```ts + * declare module "typedoc" { + * interface TranslatableStrings { + * // Define a translatable string with no arguments + * plugin_msg: []; + * // Define a translatable string requiring one argument + * plugin_msg_0: [string]; + * } + * } + * ``` + */ +export interface TranslatableStrings extends BuiltinTranslatableStringArgs {} + +declare const TranslatedString: unique symbol; +export type TranslatedString = string & { [TranslatedString]: true }; + +/** + * Dynamic proxy type built from {@link TranslatableStrings} + */ +export type TranslationProxy = { + [K in keyof TranslatableStrings]: ( + ...args: TranslatableStrings[K] + ) => TranslatedString; +}; + +// If we're running in ts-node, then we need the TS source rather than +// the compiled file. +const ext = process[Symbol.for("ts-node.register.instance") as never] + ? "cts" + : "cjs"; + +/** + * Simple internationalization module which supports placeholders. + * See {@link TranslatableStrings} for a description of how this module works and how + * plugins should add translations. + */ +export class Internationalization { + private allTranslations = new DefaultMap>( + (lang) => { + // Make sure this isn't abused to load some random file by mistake + ok( + /^[A-Za-z-]+$/.test(lang), + "Locale names may only contain letters and dashes", + ); + try { + return new Map( + // eslint-disable-next-line @typescript-eslint/no-var-requires + Object.entries(require(`./locales/${lang}.${ext}`)), + ); + } catch { + return new Map(); + } + }, + ); + + /** + * Proxy object which supports dynamically translating + * all supported keys. This is generally used rather than the translate + * method so that renaming a key on the `translatable` object that contains + * all of the default translations will automatically update usage locations. + */ + proxy: TranslationProxy = new Proxy(this, { + get(internationalization, key) { + return (...args: string[]) => + internationalization.translate( + key as never, + ...(args as never), + ); + }, + }) as never as TranslationProxy; + + /** + * If constructed without an application, will use the default language. + * Intended for use in unit tests only. + * @internal + */ + constructor(private application: Application | null) {} + + /** + * Get the translation of the specified key, replacing placeholders + * with the arguments specified. + */ + translate( + key: T, + ...args: TranslatableStrings[T] + ): TranslatedString { + return ( + this.allTranslations.get(this.application?.lang ?? "en").get(key) ?? + translatable[key] + ).replace(/\{(\d+)\}/g, (_, index) => { + return args[+index] ?? "(no placeholder)"; + }) as TranslatedString; + } + + kindSingularString(kind: ReflectionKind): TranslatedString { + switch (kind) { + case ReflectionKind.Project: + return this.proxy.kind_project(); + case ReflectionKind.Module: + return this.proxy.kind_module(); + case ReflectionKind.Namespace: + return this.proxy.kind_namespace(); + case ReflectionKind.Enum: + return this.proxy.kind_enum(); + case ReflectionKind.EnumMember: + return this.proxy.kind_enum_member(); + case ReflectionKind.Variable: + return this.proxy.kind_variable(); + case ReflectionKind.Function: + return this.proxy.kind_function(); + case ReflectionKind.Class: + return this.proxy.kind_class(); + case ReflectionKind.Interface: + return this.proxy.kind_interface(); + case ReflectionKind.Constructor: + return this.proxy.kind_constructor(); + case ReflectionKind.Property: + return this.proxy.kind_property(); + case ReflectionKind.Method: + return this.proxy.kind_method(); + case ReflectionKind.CallSignature: + return this.proxy.kind_call_signature(); + case ReflectionKind.IndexSignature: + return this.proxy.kind_index_signature(); + case ReflectionKind.ConstructorSignature: + return this.proxy.kind_constructor_signature(); + case ReflectionKind.Parameter: + return this.proxy.kind_parameter(); + case ReflectionKind.TypeLiteral: + return this.proxy.kind_type_literal(); + case ReflectionKind.TypeParameter: + return this.proxy.kind_type_parameter(); + case ReflectionKind.Accessor: + return this.proxy.kind_accessor(); + case ReflectionKind.GetSignature: + return this.proxy.kind_get_signature(); + case ReflectionKind.SetSignature: + return this.proxy.kind_set_signature(); + case ReflectionKind.TypeAlias: + return this.proxy.kind_type_alias(); + case ReflectionKind.Reference: + return this.proxy.kind_reference(); + case ReflectionKind.Document: + return this.proxy.kind_document(); + } + } + + kindPluralString(kind: ReflectionKind): TranslatedString { + switch (kind) { + case ReflectionKind.Project: + return this.proxy.kind_plural_project(); + case ReflectionKind.Module: + return this.proxy.kind_plural_module(); + case ReflectionKind.Namespace: + return this.proxy.kind_plural_namespace(); + case ReflectionKind.Enum: + return this.proxy.kind_plural_enum(); + case ReflectionKind.EnumMember: + return this.proxy.kind_plural_enum_member(); + case ReflectionKind.Variable: + return this.proxy.kind_plural_variable(); + case ReflectionKind.Function: + return this.proxy.kind_plural_function(); + case ReflectionKind.Class: + return this.proxy.kind_plural_class(); + case ReflectionKind.Interface: + return this.proxy.kind_plural_interface(); + case ReflectionKind.Constructor: + return this.proxy.kind_plural_constructor(); + case ReflectionKind.Property: + return this.proxy.kind_plural_property(); + case ReflectionKind.Method: + return this.proxy.kind_plural_method(); + case ReflectionKind.CallSignature: + return this.proxy.kind_plural_call_signature(); + case ReflectionKind.IndexSignature: + return this.proxy.kind_plural_index_signature(); + case ReflectionKind.ConstructorSignature: + return this.proxy.kind_plural_constructor_signature(); + case ReflectionKind.Parameter: + return this.proxy.kind_plural_parameter(); + case ReflectionKind.TypeLiteral: + return this.proxy.kind_plural_type_literal(); + case ReflectionKind.TypeParameter: + return this.proxy.kind_plural_type_parameter(); + case ReflectionKind.Accessor: + return this.proxy.kind_plural_accessor(); + case ReflectionKind.GetSignature: + return this.proxy.kind_plural_get_signature(); + case ReflectionKind.SetSignature: + return this.proxy.kind_plural_set_signature(); + case ReflectionKind.TypeAlias: + return this.proxy.kind_plural_type_alias(); + case ReflectionKind.Reference: + return this.proxy.kind_plural_reference(); + case ReflectionKind.Document: + return this.proxy.kind_plural_document(); + } + } + + flagString(flag: ReflectionFlag): TranslatedString { + switch (flag) { + case ReflectionFlag.None: + throw new Error("Should be unreachable"); + case ReflectionFlag.Private: + return this.proxy.flag_private(); + case ReflectionFlag.Protected: + return this.proxy.flag_protected(); + case ReflectionFlag.Public: + return this.proxy.flag_public(); + case ReflectionFlag.Static: + return this.proxy.flag_static(); + case ReflectionFlag.External: + return this.proxy.flag_external(); + case ReflectionFlag.Optional: + return this.proxy.flag_optional(); + case ReflectionFlag.Rest: + return this.proxy.flag_rest(); + case ReflectionFlag.Abstract: + return this.proxy.flag_abstract(); + case ReflectionFlag.Const: + return this.proxy.flag_const(); + case ReflectionFlag.Readonly: + return this.proxy.flag_readonly(); + case ReflectionFlag.Inherited: + return this.proxy.flag_inherited(); + } + } + + translateTagName(tag: `@${string}`): TranslatedString { + const tagName = tag.substring(1); + const translations = this.allTranslations.get( + this.application?.lang ?? "en", + ); + if (translations.has(`tag_${tagName}`)) { + return translations.get(`tag_${tagName}`) as TranslatedString; + } + // In English, the tag names are the translated names, once turned + // into title case. + return (tagName.substring(0, 1).toUpperCase() + + tagName + .substring(1) + .replace( + /[a-z][A-Z]/g, + (x) => `${x[0]} ${x[1]}`, + )) as TranslatedString; + } + + /** + * Add translations for a string which will be displayed to the user. + */ + addTranslations( + lang: string, + translations: Partial>, + override = false, + ): void { + const target = this.allTranslations.get(lang); + for (const [key, val] of Object.entries(translations)) { + if (!target.has(key) || override) { + target.set(key, val); + } + } + } + + /** + * Checks if we have any translations in the specified language. + */ + hasTranslations(lang: string): boolean { + return this.allTranslations.get(lang).size > 0; + } + + /** + * Gets a list of all languages with at least one translation. + */ + getSupportedLanguages(): string[] { + return unique([ + ...readdirSync(join(__dirname, "locales")).map((x) => + x.substring(0, x.indexOf(".")), + ), + ...this.allTranslations.keys(), + ]).sort(); + } +} diff --git a/src/lib/internationalization/locales/en.cts b/src/lib/internationalization/locales/en.cts new file mode 100644 index 000000000..828bea505 --- /dev/null +++ b/src/lib/internationalization/locales/en.cts @@ -0,0 +1,3 @@ +import { translatable } from "../translatable"; + +export = translatable; diff --git a/src/lib/internationalization/locales/jp.cts b/src/lib/internationalization/locales/jp.cts new file mode 100644 index 000000000..d7ffd796f --- /dev/null +++ b/src/lib/internationalization/locales/jp.cts @@ -0,0 +1,492 @@ +import { buildIncompleteTranslation } from "../translatable"; + +export = buildIncompleteTranslation({ + loaded_multiple_times_0: + "TypeDoc が複数回読み込まれました。これは通常、TypeDoc を独自にインストールしたプラグインによって発生します。読み込まれたパスは次のとおりです:\n{0}", + unsupported_ts_version_0: + "サポートされていない TypeScript バージョンで実行されています。TypeDoc がクラッシュした場合は、これが原因です。TypeDoc は {0} をサポートしています。", + no_compiler_options_set: + "コンパイラオプションが設定されていません。これは、TypeDoc が tsconfig.json を見つけられなかったことを意味します。生成されたドキュメントはおそらく空になります。", + loaded_plugin_0: "プラグイン {0} が読み込まれました", + solution_not_supported_in_watch_mode: + "提供された tsconfig ファイルはソリューション スタイルの tsconfig のように見えますが、これはウォッチ モードではサポートされていません。", + strategy_not_supported_in_watch_mode: + "ウォッチモードの場合、entryPointStrategy は、resolve または expand のいずれかに設定する必要があります。", + found_0_errors_and_1_warnings: + "{0} 件のエラーと {1} 件の警告が見つかりました", + docs_could_not_be_generated: + "上記のエラーのためドキュメントを生成できませんでした", + docs_generated_at_0: "{0} で生成されたドキュメント", + json_written_to_0: "JSON が {0} に書き込まれました", + no_entry_points_for_packages: + "パッケージ モードにエントリ ポイントが提供されていないため、ドキュメントを生成できません", + failed_to_find_packages: + "パッケージが見つかりませんでした。package.json を含むエントリ ポイントとして少なくとも 1 つのディレクトリを指定していることを確認してください。", + nested_packages_unsupported_0: + "{0} のプロジェクトでは entryPointStrategy がパッケージに設定されていますが、ネストされたパッケージはサポートされていません", + previous_error_occurred_when_reading_options_for_0: + "前のエラーは、{0} のパッケージのオプションを読み取り中に発生しました", + converting_project_at_0: "{0} のプロジェクトを変換しています", + failed_to_convert_packages: + "1 つ以上のパッケージの変換に失敗しました。結果は結合されません。", + merging_converted_projects: "変換されたプロジェクトのマージ", + no_entry_points_to_merge: + "マージするためのエントリポイントが提供されていません", + entrypoint_did_not_match_files_0: + "エントリポイント グロブ {0} はどのファイルにも一致しませんでした", + failed_to_parse_json_0: "{0} のファイルを json として解析できませんでした", + failed_to_read_0_when_processing_document_tag_in_1: + "{1} のコメントの @document タグの処理中にファイル {0} の読み取りに失敗しました", + failed_to_read_0_when_processing_project_document: + "プロジェクト ドキュメントの追加時にファイル {0} の読み取りに失敗しました", + failed_to_read_0_when_processing_document_child_in_1: + "{1} 内のドキュメントの子を処理するときにファイル {0} の読み取りに失敗しました", + frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values: + "{0} の Frontmatter の子は、文字列の配列または文字列値を持つオブジェクトである必要があります。", + converting_union_as_interface: + "ユニオン型で@interfaceを使用すると、ユニオンのすべてのブランチに存在しないプロパティが破棄されます。TypeDocの出力はソースコードを正確に記述しない可能性があります。", + converting_0_as_class_requires_value_declaration: + "{0} をクラスとして変換するには、非型値を表す宣言が必要です", + converting_0_as_class_without_construct_signatures: + "{0} はクラスとして変換されていますが、コンストラクト シグネチャがありません", + comment_for_0_should_not_contain_block_or_modifier_tags: + "{0} のコメントにはブロックタグや修飾タグを含めることはできません", + symbol_0_has_multiple_declarations_with_comment: + "{0} にはコメント付きの宣言が複数あります。任意のコメントが使用されます", + comments_for_0_are_declared_at_1: + "{0} のコメントは次の場所で宣言されています:\n{1}", + multiple_type_parameters_on_template_tag_unsupported: + "TypeDoc は、コメント付きの単一の @template タグで定義された複数の型パラメータをサポートしていません。", + failed_to_find_jsdoc_tag_for_name_0: + "コメントを解析した後、{0} の JSDoc タグが見つかりませんでした。バグレポートを提出してください。", + // relative_path_0_is_not_a_file_and_will_not_be_copied_to_output + inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0: + "インライン @inheritDoc タグはブロック タグ内に出現しないでください。{0} のコメントでは処理されません。", + at_most_one_remarks_tag_expected_in_comment_at_0: + "コメントには最大 1 つの @remarks タグが必要です。{0} のコメントの最初のタグ以外はすべて無視されます。", + at_most_one_returns_tag_expected_in_comment_at_0: + "コメントには最大 1 つの @returns タグが必要です。{0} のコメントの最初のタグ以外はすべて無視されます。", + at_most_one_inheritdoc_tag_expected_in_comment_at_0: + "コメントには最大 1 つの @inheritDoc タグが必要です。{0} のコメントの最初のタグ以外はすべて無視されます。", + content_in_summary_overwritten_by_inheritdoc_in_comment_at_0: + "概要セクションの内容は、{0} のコメントの @inheritDoc タグによって上書きされます。", + content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0: + "@remarks ブロックの内容は、{0} のコメントの @inheritDoc タグによって上書きされます。", + example_tag_literal_name: + "サンプルタグの最初の行はサンプル名として文字通り解釈され、テキストのみを含む必要があります。", + inheritdoc_tag_properly_capitalized: + "@inheritDocタグは適切に大文字にする必要があります", + treating_unrecognized_tag_0_as_modifier: + "認識されないタグ {0} を修飾タグとして処理します", + unmatched_closing_brace: "一致しない閉じ括弧", + unescaped_open_brace_without_inline_tag: + "インラインタグのないエスケープされていない開き括弧が検出されました", + unknown_block_tag_0: "不明なブロック タグ {0} に遭遇しました", + unknown_inline_tag_0: "不明なインライン タグ {0} に遭遇しました", + open_brace_within_inline_tag: + "インラインタグ内に開き括弧が見つかりました。これはおそらく間違いです", + inline_tag_not_closed: "インラインタグが閉じられていない", + failed_to_resolve_link_to_0_in_comment_for_1: + "{1} のコメント内の「{0}」へのリンクを解決できませんでした", + type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs: + "{1} で定義されている {0} は {2} によって参照されていますが、ドキュメントには含まれていません。", + reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: + "{2} で定義されている {0} ({1}) にはドキュメントがありません", + invalid_intentionally_not_exported_symbols_0: + "次のシンボルは意図的にエクスポートされないものとしてマークされていますが、ドキュメントで参照されていないか、エクスポートされています:\n{0}", + not_all_search_category_boosts_used_0: + "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}」の @categoryDe​​scription が含まれていますが、そのカテゴリに子が配置されていません", + comment_for_0_includes_groupDescription_for_1_but_no_child_in_group: + '{0} のコメントに "{1}" の @groupDescription が含まれていますが、そのグループには子が配置されていません', + label_0_for_1_cannot_be_referenced: + '{1} のラベル "{0}" は宣言参照では参照できません。ラベルには A ~ Z、0 ~ 9、_ のみを含めることができ、数字で始まることはできません。', + failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: `{1} のコメント内の "{0}" へのリンクを解決できません。"{2}" を意味していた可能性があります。`, + failed_to_resolve_link_to_0_in_readme_for_1: `{1} の README ファイル内の "{0}" へのリンクを解決できません。`, + failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2: `{1} の README ファイル内の "{0}" へのリンクを解決できません。"{2}" を意味していた可能性があります。`, + modifier_tag_0_is_mutually_exclusive_with_1_in_comment_for_2: + "修飾子タグ {0} は、{2} のコメント内の {1} と相互に排他的です", + signature_0_has_unused_param_with_name_1: + '署名 {0} には、名前が "{1}" の @param がありますが、使用されていません。', + declaration_reference_in_inheritdoc_for_0_not_fully_parsed: + "{0} の @inheritDoc の宣言参照が完全に解析されていないため、正しく解決されない可能性があります", + failed_to_find_0_to_inherit_comment_from_in_1: + "{1} のコメントからコメントを継承する「{0}」が見つかりませんでした", + reflection_0_tried_to_copy_comment_from_1_but_source_had_no_comment: + "{0} は @inheritDoc を使用して {1} からコメントをコピーしようとしましたが、ソースには関連付けられたコメントがありません", + inheritdoc_circular_inheritance_chain_0: + "@inheritDoc は循環継承チェーンを指定します: {0}", + provided_readme_at_0_could_not_be_read: + "指定された README パス、{0} は読み取れませんでした", + defaulting_project_name: + "--name オプションが指定されておらず、package.json が見つかりませんでした。プロジェクト名を「Documentation」にデフォルト設定します。", + disable_git_set_but_not_source_link_template: + "enableGit は設定されていますが、sourceLinkTemplate が設定されていないため、ソースリンクを生成できません。ソースの追跡を防止するには、sourceLinkTemplate または enableSources を設定します。", + disable_git_set_and_git_revision_used: + "enableGit が設定されており、sourceLinkTemplate に {gitRevision} が含まれていますが、リビジョンが提供されていないため、空の文字列に置き換えられます。", + git_remote_0_not_valid: + '提供された Git リモート "{0}" は無効です。ソース リンクは壊れます', + custom_css_file_0_does_not_exist: + "{0} のカスタム CSS ファイルは存在しません", + unsupported_highlight_language_0_not_highlighted_in_comment_for_1: + "サポートされていないハイライト言語 {0} は、{1} のコメントではハイライトされません。", + unloaded_language_0_not_highlighted_in_comment_for_1: + "言語 {0} のコード ブロックは、highlightLanguages オプションに含まれていないため、{1} のコメントでは強調表示されません。", + yaml_frontmatter_not_an_object: + "YAML フロントマターはオブジェクトであると想定されます", + could_not_write_0: "{0} を書き込めませんでした", + could_not_empty_output_directory_0: + "出力ディレクトリ {0} を空にできませんでした", + could_not_create_output_directory_0: + "出力ディレクトリ {0} を作成できませんでした", + theme_0_is_not_defined_available_are_1: + "テーマ '{0}' は定義されていません。使用可能なテーマは次のとおりです: {1}", + custom_theme_does_not_define_getSlugger: + "カスタムテーマはgetSlugger(reflection)メソッドを定義していませんが、マークダウンをレンダリングしようとします", + no_entry_points_provided: + "エントリポイントが提供されていません。これは設定ミスである可能性があります。", + unable_to_find_any_entry_points: + "エントリ ポイントが見つかりません。以前の警告を参照してください", + watch_does_not_support_packages_mode: + "ウォッチモードは「パッケージ」スタイルのエントリポイントをサポートしていません", + watch_does_not_support_merge_mode: + "ウォッチモードでは「マージ」スタイルのエントリポイントはサポートされません", + entry_point_0_not_in_program: + "エントリ ポイント {0} は、tsconfig の 'files' または 'include' オプションによって参照されていません。", + use_expand_or_glob_for_files_in_dir: + "このディレクトリ内のファイルを含める場合は、--entryPointStrategyを設定して展開するか、globを指定します。", + glob_0_did_not_match_any_files: + "グロブ {0} はどのファイルにも一致しませんでした", + entry_point_0_did_not_match_any_files_after_exclude: + "除外パターンを適用した後、グロブ {0} はどのファイルにも一致しませんでした", + entry_point_0_did_not_exist: + "指定されたエントリ ポイント {0} は存在しません", + entry_point_0_did_not_match_any_packages: + "エントリ ポイント glob {0} は、package.json を含むディレクトリと一致しませんでした。", + 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 + 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}' オプションは設定ファイル経由でのみ指定できます", + option_0_expected_a_value_but_none_provided: + "--{0} には値が期待されていましたが、引数として値が与えられませんでした", + unknown_option_0_may_have_meant_1: + "不明なオプション: {0}。次のオプションを意味している可能性があります:\n{1}", + typedoc_key_in_0_ignored: + "{0} の 'typedoc' キーは、レガシー パッケージの entryPointStrategy によって使用されており、無視されます。", + typedoc_options_must_be_object_in_0: + '{0} の "typedocOptions" フィールドを解析できませんでした。フィールドが存在し、オブジェクトが含まれていることを確認してください。', + tsconfig_file_0_does_not_exist: "tsconfig ファイル {0} が存在しません", + tsconfig_file_specifies_options_file: + "tsconfig ファイルの「typedocOptions」は、読み取るオプション ファイルを指定していますが、オプション ファイルは既に読み取られています。これは、設定ミスである可能性があります。", + tsconfig_file_specifies_tsconfig_file: + 'tsconfig ファイルの "typedocOptions" で、読み取る tsconfig ファイルを指定していない可能性があります。', + tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json: + "typedoc.json で定義された {0} は、tsdoc.json の設定によって上書きされます。", + failed_read_tsdoc_json_0: + "{0} の tsdoc.json ファイルの読み取りに失敗しました", + invalid_tsdoc_json_0: + "ファイル {0} は有効な tsdoc.json ファイルではありません", + options_file_0_does_not_exist: "オプションファイル {0} が存在しません", + failed_read_options_file_0: + "{0} の解析に失敗しました。存在し、オブジェクトをエクスポートしていることを確認してください", + invalid_plugin_0_missing_load_function: + "プラグイン {0} の構造が無効です。ロード関数が見つかりません", + plugin_0_could_not_be_loaded: "プラグイン {0} を読み込めませんでした", + help_options: + "読み込むべき json オプション ファイルを指定します。指定しない場合、TypeDoc は現在のディレクトリで 'typedoc.json' を検索します。", + help_tsconfig: + "読み込む TypeScript 設定ファイルを指定します。指定しない場合、TypeDoc は現在のディレクトリで 'tsconfig.json' を検索します。", + help_compilerOptions: + "TypeDoc で使用される TypeScript コンパイラ オプションを選択的にオーバーライドします。", + help_lang: "生成時およびTypeDocのメッセージで使用する言語を設定します", + help_locales: + "指定されたロケールの翻訳を追加します。このオプションは、TypeDoc に公式のロケール サポートが追加されるまでの暫定的な手段として主に使用されます。", + help_packageOptions: + "entryPointStrategy がパッケージに設定されている場合に各パッケージ内で設定されるオプションを設定します。", + help_entryPoints: "ドキュメントのエントリポイント", + help_entryPointStrategy: + "エントリポイントをドキュメントモジュールに変換するために使用する戦略", + help_alwaysCreateEntryPointModule: + "設定すると、TypeDoc はエントリ ポイントに `Module` を常に作成します (1 つしか提供されていない場合でも)。", + help_projectDocuments: + "生成されたドキュメントのルートに子として追加されるドキュメント。複数のファイルに一致する glob をサポートします。", + help_exclude: + "エントリポイントとして指定されたディレクトリを展開するときに除外するパターンを定義します", + help_externalPattern: + "外部ファイルとみなすべきファイルのパターンを定義する", + help_excludeExternals: "外部で解決されたシンボルが文書化されないようにする", + help_excludeNotDocumented: + "明示的に文書化されていないシンボルが結果に表示されないようにする", + help_excludeNotDocumentedKinds: + "excludeNotDocumented によって削除できる反射の種類を指定します", + help_excludeInternal: + "@internal でマークされたシンボルがドキュメント化されないようにする", + help_excludeCategories: + "このカテゴリ内のシンボルをドキュメントから除外する", + help_excludePrivate: + "プライベート変数とメソッドを無視します。デフォルトは true です。", + help_excludeProtected: "保護された変数とメソッドを無視する", + help_excludeReferences: + "シンボルが複数回エクスポートされた場合、最初のエクスポート以外はすべて無視されます。", + help_externalSymbolLinkMappings: + "ドキュメントに含まれていないシンボルのカスタムリンクを定義する", + help_out: "ドキュメントを書き込む場所を指定します", + help_json: + "プロジェクトを説明するJSONファイルが書き込まれる場所とファイル名を指定します", + help_pretty: "出力JSONをタブでフォーマットするかどうかを指定します", + help_emit: + "TypeDoc が発行する内容を指定します (「docs」、「both」、または「none」)", + help_theme: "ドキュメントをレンダリングするテーマ名を指定します", + help_lightHighlightTheme: "ライトモードでコード強調テーマを指定する", + help_darkHighlightTheme: "ダークモードでのコード強調テーマを指定する", + help_highlightLanguages: + "レンダリング時にコードを強調表示するために読み込まれる言語を指定します", + help_customCss: "テーマをインポートするためのカスタム CSS ファイルへのパス", + help_markdownItOptions: + "TypeDocが使用するMarkdownパーサーであるmarkdown-itに渡されるオプションを指定します。", + help_markdownItLoader: + "markdown-itインスタンスをロードするときに呼び出されるコールバックを指定します。TypeDocが使用するパーサーのインスタンスが渡されます。", + help_maxTypeConversionDepth: "変換する型の最大深度を設定する", + help_name: "テンプレートのヘッダーで使用されるプロジェクト名を設定します", + help_includeVersion: "プロジェクト名にパッケージバージョンを追加する", + help_disableSources: "反射を文書化するときに反射のソースの設定を無効にする", + help_sourceLinkTemplate: + "ソース URL を生成するときに使用するリンク テンプレートを指定します。設定されていない場合は、git リモートを使用して自動的に作成されます。{path}、{line}、{gitRevision} プレースホルダーをサポートします。", + help_gitRevision: + "GitHub/Bitbucket ソースファイルへのリンクに、最後のリビジョンではなく指定されたリビジョンを使用します。disableSources が設定されている場合は効果がありません。", + help_gitRemote: + "GitHub/Bitbucket ソースファイルへのリンクに指定されたリモートを使用します。disableGit またはdisableSources が設定されている場合は効果がありません。", + help_disableGit: + "すべてが sourceLinkTemplate でリンクできると仮定します。これが有効な場合は、sourceLinkTemplate を設定する必要があります。{path} は basePath をルートとします。", + help_basePath: "ファイルパスを表示するときに使用するベースパスを指定します", + help_excludeTags: + "ドキュメントコメントからリストされたブロック/修飾子タグを削除します", + help_readme: + "インデックス ページに表示される Readme ファイルへのパス。インデックス ページを無効にしてグローバル ページでドキュメントを開始するには、`none` を渡します。", + help_cname: + "CNAMEファイルのテキストを設定します。これはGitHub Pagesのカスタムドメインに便利です。", + help_sourceLinkExternal: + "ソースリンクを外部リンクとして扱い、新しいタブで開くように指定します。", + help_githubPages: + "GitHub Pages で 404 エラーを防ぐために .nojekyll ファイルを生成します。デフォルトは `true` です。", + help_hostedBaseUrl: + "出力フォルダ内の sitemap.xml と正規リンクを生成する際に使用するベース URL を指定します。指定しない場合は、サイトマップは生成されません。", + help_useHostedBaseUrlForAbsoluteLinks: + "設定されている場合、TypeDocはhostedBaseUrlオプションを使用してサイト上のページへの絶対リンクを生成します。", + help_hideGenerator: + "ページの最後にある TypeDoc リンクを印刷しないでください", + help_customFooterHtml: "TypeDoc リンクの後のカスタム フッター", + help_customFooterHtmlDisableWrapper: + "設定されている場合、customFooterHtml のラッパー要素が無効になります。", + help_hideParameterTypesInTitle: + "署名タイトルのパラメータタイプを非表示にしてスキャンしやすくします", + help_cacheBust: "静的アセットへのリンクに生成時間を含める", + help_searchInComments: + "設定すると、検索インデックスにコメントも含まれます。これにより、検索インデックスのサイズが大幅に増加します。", + help_searchInDocuments: + "設定すると、検索インデックスにドキュメントも含まれます。これにより、検索インデックスのサイズが大幅に増加します。", + help_cleanOutputDir: + "設定されている場合、TypeDocは出力を書き込む前に出力ディレクトリを削除します。", + help_titleLink: + "ヘッダーのタイトルが指すリンクを設定します。デフォルトはドキュメントのホームページです。", + help_navigationLinks: "ヘッダーに含めるリンクを定義します", + help_sidebarLinks: "サイドバーに含めるリンクを定義します", + help_navigationLeaves: "展開すべきでないナビゲーションツリーのブランチ", + help_navigation: "ナビゲーションサイドバーの構成方法を決定します", + help_visibilityFilters: + "修飾タグに応じて組み込みフィルターと追加フィルターのデフォルトの表示を指定します", + help_searchCategoryBoosts: + "選択したカテゴリの関連性を高めるために検索を設定する", + help_searchGroupBoosts: + "選択した種類(例:「クラス」)の関連性を高めるように検索を設定します", + help_jsDocCompatibility: + "JSDocコメントとの類似性を高めるコメント解析の互換性オプションを設定します", + help_commentStyle: "TypeDoc がコメントを検索する方法を決定します", + help_useTsLinkResolution: + "@linkタグが指す場所を決定する際にTypeScriptのリンク解決を使用します。これはJSDocスタイルのコメントにのみ適用されます。", + help_preserveLinkText: + "設定されている場合、リンクテキストのない@linkタグはテキストコンテンツをリンクとして使用します。設定されていない場合は、ターゲットリフレクション名を使用します。", + help_blockTags: "コメントを解析するときに TypeDoc が認識するブロックタグ", + help_inlineTags: "TypeDoc がコメントを解析する際に認識するインラインタグ", + help_modifierTags: "TypeDoc がコメントを解析する際に認識する修飾タグ", + help_categorizeByGroup: "グループレベルで分類を行うかどうかを指定します", + help_defaultCategory: "カテゴリのない反射のデフォルトカテゴリを指定します", + help_categoryOrder: + "カテゴリの表示順序を指定します。* はリストにないカテゴリの相対順序を示します。", + help_groupOrder: + "グループの表示順序を指定します。* はリストにないグループの相対順序を示します。", + help_sort: "文書化された値のソート戦略を指定する", + help_sortEntryPoints: + "設定されている場合、エントリポイントは他のリフレクションと同じソートルールに従います。", + help_kindSortOrder: + "「kind」が指定されている場合、反射のソート順を指定します", + help_watch: + "ファイルの変更を監視し、変更があった場合はドキュメントを再構築する", + help_preserveWatchOutput: + "設定されている場合、TypeDoc はコンパイル実行間で画面をクリアしません。", + help_skipErrorChecking: + "ドキュメントを生成する前にTypeScriptの型チェックを実行しない", + help_help: "このメッセージを印刷する", + help_version: "TypeDocのバージョンを印刷", + help_showConfig: "解決された構成を印刷して終了する", + help_plugin: + "ロードするnpmプラグインを指定します。省略すると、インストールされているすべてのプラグインがロードされます。", + help_logLevel: "使用するログレベルを指定する", + help_treatWarningsAsErrors: + "設定すると、すべての警告がエラーとして扱われます", + help_treatValidationWarningsAsErrors: + "設定すると、検証中に発行された警告はエラーとして扱われます。このオプションは、検証警告の treatWarningsAsErrors を無効にするために使用することはできません。", + help_intentionallyNotExported: + "「参照されているが文書化されていない」という警告を生成しないタイプのリスト", + help_requiredToBeDocumented: "文書化する必要がある反射の種類のリスト", + help_validation: + "生成されたドキュメントに対して TypeDoc が実行する検証手順を指定します。", + unknown_option_0_you_may_have_meant_1: + "不明なオプション '{0}' 次のオプションを意味している可能性があります:\n{1}", + option_0_must_be_between_1_and_2: + "{0} は {1} と {2} の間でなければなりません", + option_0_must_be_equal_to_or_greater_than_1: + "{0} は {1} 以上である必要があります", + option_0_must_be_less_than_or_equal_to_1: + "{0} は {1} 以下である必要があります", + option_0_must_be_one_of_1: "{0} は {1} のいずれかである必要があります", + flag_0_is_not_valid_for_1_expected_2: + "フラグ '{0}' は {1} に対して有効ではありません。{2} のいずれかである必要があります。", + expected_object_with_flag_values_for_0: + "{0} または true/false のフラグ値を持つオブジェクトが必要です", + flag_values_for_0_must_be_booleans: + "{0} のフラグ値はブール値である必要があります", + locales_must_be_an_object: + "'locales' オプションは、次のようなオブジェクトに設定する必要があります: { en: { theme_implements: \"Implements\" }}", + exclude_not_documented_specified_0_valid_values_are_1: + "excludeNotDocumentedKinds は既知の値のみを指定できますが、無効な値が指定されました ({0})。有効な種類は次のとおりです:\n{1}", + external_symbol_link_mappings_must_be_object: + "externalSymbolLinkMappings は、Record<パッケージ名、Record<シンボル名、リンク>> である必要があります。", + highlight_theme_0_must_be_one_of_1: + "{0} は次のいずれかである必要があります: {1}", + highlightLanguages_contains_invalid_languages_0: + "highlightLanguages に無効な言語が含まれています: {0}。サポートされている言語のリストについては typedoc --help を実行してください", + hostedBaseUrl_must_start_with_http: + "hostedBaseUrl は http:// または https:// で始まる必要があります", + useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl: + "useHostedBaseUrlForAbsoluteLinksオプションではhostedBaseUrlを設定する必要があります", + option_0_must_be_an_object: + "'{0}' オプションは配列以外のオブジェクトである必要があります", + option_0_must_be_a_function: "'{0}' オプションは関数である必要があります", + option_0_must_be_object_with_urls: + "{0} は、キーとして文字列ラベル、URL 値として文字列ラベルを持つオブジェクトである必要があります。", + visibility_filters_only_include_0: + "visibilityFilters には、次の非 @ キーのみを含めることができます: {0}", + visibility_filters_must_be_booleans: + "visibilityFilters のすべての値はブール値である必要があります", + option_0_values_must_be_numbers: + "{0} のすべての値は数値である必要があります", + option_0_values_must_be_array_of_tags: + "{0} は有効なタグ名の配列である必要があります", + option_0_specified_1_but_only_2_is_valid: + "{0} は既知の値のみを指定できますが、無効な値が指定されました ({1})。有効な並べ替え戦略は次のとおりです:\n{2}", + kind_project: "プロジェクト", + kind_module: "モジュール", + kind_namespace: "名前空間", + kind_enum: "列挙", + kind_enum_member: "列挙メンバー", + kind_variable: "変数", + kind_function: "関数", + kind_class: "クラス", + kind_interface: "インターフェイス", + kind_constructor: "コンストラクター", + kind_property: "プロパティ", + kind_method: "メソッド", + kind_call_signature: "コールシグネチャ", + kind_index_signature: "インデックスシグネチャ", + kind_constructor_signature: "コンストラクターシグネチャ", + kind_parameter: "パラメーター", + kind_type_literal: "型リテラル", + kind_type_parameter: "型パラメーター", + kind_accessor: "アクセッサー", + kind_get_signature: "署名を取得する", + kind_set_signature: "署名を設定する", + kind_type_alias: "型エイリアス", + kind_reference: "リファレンス", + kind_document: "ドキュメント", + kind_plural_project: "プロジェクト", + kind_plural_module: "モジュール", + kind_plural_namespace: "名前空間", + kind_plural_enum: "列挙", + kind_plural_enum_member: "列挙メンバー", + kind_plural_variable: "変数", + kind_plural_function: "関数", + kind_plural_class: "クラス", + kind_plural_interface: "インターフェイス", + kind_plural_constructor: "コンストラクター", + kind_plural_property: "プロパティ", + kind_plural_method: "メソッド", + kind_plural_call_signature: "コールシグネチャ", + kind_plural_index_signature: "インデックスシグネチャ", + kind_plural_constructor_signature: "コンストラクターシグネチャ", + kind_plural_parameter: "パラメータ", + kind_plural_type_literal: "型リテラル", + kind_plural_type_parameter: "型パラメーター", + kind_plural_accessor: "アクセッサー", + kind_plural_get_signature: "署名を取得する", + kind_plural_set_signature: "署名を設定する", + kind_plural_type_alias: "型エイリアス", + kind_plural_reference: "リファレンス", + kind_plural_document: "ドキュメント", + flag_protected: "保護", + flag_private: "非公開", + flag_external: "外部", + flag_inherited: "継承", + flag_public: "公開", + flag_static: "静的", + flag_optional: "オプション", + flag_rest: "REST パラメータ", + flag_abstract: "抽象", + flag_const: "定数", + flag_readonly: "読み取り専用", + theme_implements: "実装", + theme_indexable: "インデックス可能", + theme_type_declaration: "型宣言", + theme_index: "インデックス", + theme_hierarchy: "階層", + theme_hierarchy_view_full: "完全な階層を表示", + theme_implemented_by: "実装者", + theme_defined_in: "定義", + theme_implementation_of: "の実装", + theme_inherited_from: "継承元", + theme_overrides: "上書き", + theme_returns: "戻り値", + theme_re_exports: "再エクスポート", + theme_renames_and_re_exports: "リネームと再エクスポート", + theme_generated_using_typedoc: "TypeDocを使用して生成", + theme_preparing_search_index: "検索インデックスを準備しています...", + theme_search_index_not_available: "検索インデックスは利用できません", + theme_settings: "テーマ設定", + theme_member_visibility: "メンバーの可視性", + theme_theme: "配色", + theme_os: "自動", + theme_light: "ライト", + theme_dark: "ダーク", + theme_on_this_page: "このページ", + theme_search: "検索", + theme_menu: "メニュー", + theme_permalink: "パーマリンク", + tag_see: "参照", + tag_group: "所属グループ", + tag_example: "例", + theme_copy: "コピー", + theme_copied: "コピー完了!", + theme_normally_hidden: + "このメンバーは、フィルター設定のため、通常は非表示になっています。", + theme_class_hierarchy_title: "クラス継承図", + theme_loading: "読み込み中...", +}); diff --git a/src/lib/internationalization/locales/ko.cts b/src/lib/internationalization/locales/ko.cts new file mode 100644 index 000000000..471be8de1 --- /dev/null +++ b/src/lib/internationalization/locales/ko.cts @@ -0,0 +1,342 @@ +import { buildIncompleteTranslation } from "../translatable"; + +export = buildIncompleteTranslation({ + docs_generated_at_0: "문서가 {0}에 생성되었습니다", + json_written_to_0: "{0}에 JSON이 작성되었습니다", + + no_entry_points_for_packages: + "패키지 모드에 대한 진입점이 제공되지 않았으므로 문서를 생성할 수 없습니다", + failed_to_find_packages: + "패키지를 찾지 못했습니다. 적어도 하나의 디렉터리를 package.json을 포함하는 진입점으로 제공했는지 확인하세요", + nested_packages_unsupported_0: + "{0} 프로젝트의 entryPointStrategy가 패키지인데 중첩된 패키지는 지원되지 않습니다", + previous_error_occurred_when_reading_options_for_0: + "{0} 위치의 패키지 옵션을 읽는 중에 이전 오류가 발생했습니다", + converting_project_at_0: "{0} 위치의 프로젝트 변환 중", + failed_to_convert_packages: + "하나 이상의 패키지를 변환하지 못했습니다. 결과가 병합되지 않을 것입니다", + merging_converted_projects: "변환된 프로젝트 병합 중", + + no_entry_points_to_merge: "병합할 진입점이 제공되지 않았습니다", + entrypoint_did_not_match_files_0: + "진입점 글로브 {0}이(가) 어떤 파일과도 일치하지 않았습니다", + frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values: + "{0}의 Frontmatter children은 문자열 배열이나 문자열 값을 갖는 객체여야 합니다", + + inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0: + "{0} 위치의 주석에서 인라인 @inheritDoc 태그는 블록 태그 안에 나타나서는 안 됩니다", + at_most_one_remarks_tag_expected_in_comment_at_0: + "주석에서 @remarks 태그는 최대 하나만 예상됩니다. {0}", + at_most_one_returns_tag_expected_in_comment_at_0: + "주석에서 @returns 태그는 최대 하나만 예상됩니다. {0}", + at_most_one_inheritdoc_tag_expected_in_comment_at_0: + "주석에서 @inheritDoc 태그는 최대 하나만 예상됩니다. {0}", + content_in_summary_overwritten_by_inheritdoc_in_comment_at_0: + "주석에서 요약 부분의 내용이 @inheritDoc 태그에 의해 덮어쓰여집니다. {0}", + content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0: + "주석에서 @remarks 블록의 내용이 @inheritDoc 태그에 의해 덮어쓰여집니다. {0}", + example_tag_literal_name: + "예제 태그의 첫 번째 줄은 예제 이름으로 사용됩니다. 텍스트만 포함해야 합니다", + inheritdoc_tag_properly_capitalized: + "@inheritDoc 태그는 올바르게 대문자화되어야 합니다", + invalid_intentionally_not_exported_symbols_0: + "다음 심볼은 의도적으로 내보내지 않았지만 문서화에서 참조되지 않았거나 내보내졌습니다:\n\t{0}", + defaulting_project_name: + '--name 옵션이 지정되지 않았고 package.json도 발견되지 않았습니다. 프로젝트 이름을 "Documentation"으로 기본 설정합니다', + no_entry_points_provided: + "진입점이 제공되지 않았습니다. 이는 구성 오류일 가능성이 높습니다", + unable_to_find_any_entry_points: + "어떤 진입점도 찾을 수 없습니다. 이전 경고를 확인하세요", + watch_does_not_support_packages_mode: + "워치 모드는 'packages' 스타일 진입점을 지원하지 않습니다", + watch_does_not_support_merge_mode: + "워치 모드는 'merge' 스타일 진입점을 지원하지 않습니다", + help_options: + "로드할 JSON 옵션 파일을 지정합니다. 지정하지 않으면 TypeDoc은 현재 디렉터리의 'typedoc.json'을 찾습니다", + help_tsconfig: + "로드할 TypeScript 구성 파일을 지정합니다. 지정하지 않으면 TypeDoc은 현재 디렉터리의 'tsconfig.json'을 찾습니다", + help_compilerOptions: + "TypeDoc이 사용할 TypeScript 컴파일러 옵션을 선택적으로 재정의합니다", + help_lang: "생성 및 TypeDoc 메시지에 사용할 언어를 설정합니다", + help_locales: + "특정 로케일에 대한 번역을 추가합니다. 이 옵션은 주로 TypeDoc에서 공식 로케일 지원이 추가될 때까지 임시 방편으로 사용됩니다", + help_packageOptions: + "entryPointStrategy가 패키지로 설정된 경우 각 패키지에 설정될 옵션을 설정합니다", + + help_entryPoints: "문서화할 진입점입니다", + help_entryPointStrategy: + "진입점을 문서 모듈로 변환하는 데 사용할 전략입니다", + help_alwaysCreateEntryPointModule: + "설정 시 TypeDoc은 하나의 진입점만 제공되더라도 항상 'Module'을 생성합니다", + help_projectDocuments: + "생성된 문서의 루트에 추가될 문서입니다. 복수 파일을 매치하기 위한 글로브를 지원합니다", + help_exclude: "진입점으로 지정된 디렉터리 확장 시 제외할 패턴을 정의합니다", + help_externalPattern: "외부로 간주될 파일 패턴을 정의합니다", + help_excludeExternals: "외부로 해결된 심볼이 문서화되지 않도록 방지합니다", + help_excludeNotDocumented: + "명시적으로 문서화되지 않은 심볼이 결과에 표시되지 않도록 방지합니다", + help_excludeNotDocumentedKinds: + "excludeNotDocumented로 제거될 리플렉션 유형을 지정합니다", + help_excludeInternal: + "@internal로 표시된 심볼이 문서화되지 않도록 방지합니다", + help_excludeCategories: "문서에서 제외할 카테고리 내의 심볼을 제외합니다", + help_excludePrivate: + "비공개 변수와 메서드를 무시합니다. 기본값은 true입니다.", + help_excludeProtected: "보호된 변수와 메서드를 무시합니다", + help_excludeReferences: + "심볼이 여러 번 내보내진 경우 첫 번째 내보내기를 제외하고 모두 무시합니다", + help_externalSymbolLinkMappings: + "문서에 포함되지 않은 심볼에 대한 사용자 정의 링크를 정의합니다", + help_out: "문서가 쓰여질 위치를 지정합니다", + help_json: "프로젝트를 설명하는 JSON 파일의 위치와 파일 이름을 지정합니다", + help_pretty: "출력 JSON을 탭으로 포맷팅할 지 여부를 지정합니다", + help_emit: + "TypeDoc이 생성할 내용을 지정합니다. 'docs', 'both', 'none' 중 하나를 선택합니다", + help_theme: "문서를 렌더링할 테마 이름을 지정합니다", + help_lightHighlightTheme: + "라이트 모드에서 코드 하이라이팅 테마를 지정합니다", + help_darkHighlightTheme: "다크 모드에서 코드 하이라이팅 테마를 지정합니다", + help_highlightLanguages: + "렌더링 시 코드 하이라이팅에 사용될 언어를 지정합니다", + help_customCss: "테마에서 가져올 사용자 지정 CSS 파일의 경로", + help_markdownItOptions: + "TypeDoc이 사용하는 markdown-it에 전달할 옵션을 지정합니다", + help_markdownItLoader: + "markdown-it 인스턴스를 로드할 때 호출될 콜백을 지정합니다. TypeDoc이 사용할 파서 인스턴스를 전달받습니다", + help_maxTypeConversionDepth: "변환될 타입의 최대 깊이를 설정합니다", + help_name: "템플릿 헤더에 사용할 프로젝트 이름을 설정합니다", + help_includeVersion: "프로젝트 이름에 패키지 버전을 추가합니다", + help_disableSources: "문서화할 때 리플렉션의 소스 설정을 비활성화합니다", + help_sourceLinkTemplate: + "소스 URL 생성 시 사용할 링크 템플릿을 지정합니다. 설정하지 않으면 자동으로 git 원격 저장소에서 생성됩니다. {path}, {line}, {gitRevision} 플레이스홀더를 지원합니다", + help_gitRevision: + "GitHub/Bitbucket 소스 파일에 대한 링크를 생성할 때 사용할 특정 리비전을 지정합니다. disableSources가 설정된 경우에만 유효합니다", + help_gitRemote: + "GitHub/Bitbucket 소스 파일에 대한 링크를 생성할 때 사용할 특정 원격 저장소를 지정합니다. disableGit 또는 disableSources가 설정된 경우에만 유효합니다", + help_disableGit: + "모든 것을 sourceLinkTemplate로 링크할 수 있도록 가정합니다. 이 옵션을 사용하려면 sourceLinkTemplate이 설정되어 있어야 합니다. {path}는 basePath에서 시작됩니다", + help_basePath: "파일 경로를 표시할 때 사용할 기본 경로를 지정합니다", + help_excludeTags: "문서 주석에서 제거할 블록/수정자 태그를 지정합니다", + help_readme: + "인덱스 페이지에 표시할 readme 파일의 경로를 지정합니다. 'none'을 전달하여 인덱스 페이지를 비활성화하고 글로벌 페이지에서 문서화를 시작합니다", + help_cname: + "GitHub Pages의 사용자 정의 도메인에 유용한 CNAME 파일 텍스트를 설정합니다", + help_sourceLinkExternal: + "소스 링크를 외부 링크로 취급하여 새 탭에서 열도록 지정합니다", + help_githubPages: + "GitHub Pages에서 404 오류를 방지하기 위해 .nojekyll 파일을 생성합니다. 기본값은 `true`입니다", + help_hostedBaseUrl: + "생성된 sitemap.xml 및 출력 폴더에서 사용할 베이스 URL을 지정합니다. 지정하지 않으면 sitemap이 생성되지 않습니다", + help_useHostedBaseUrlForAbsoluteLinks: + "사이트의 페이지에 대해 hostedBaseUrl 옵션을 사용하여 절대 링크를 생성하도록 지정합니다", + help_hideGenerator: "페이지 끝에 TypeDoc 링크를 출력하지 않습니다", + help_customFooterHtml: "TypeDoc 링크 뒤에 사용자 정의 푸터를 지정합니다", + help_customFooterHtmlDisableWrapper: + "customFooterHtml의 래퍼 요소를 비활성화합니다", + help_hideParameterTypesInTitle: + "제목에서 매개변수 유형을 숨겨 스캔하기 쉽게합니다", + help_cacheBust: "정적 자산의 링크에 생성 시간을 포함합니다", + help_searchInComments: + "검색 인덱스에 주석도 포함합니다. 이 옵션을 사용하면 검색 인덱스의 크기가 크게 증가합니다", + help_searchInDocuments: + "검색 인덱스에 문서도 포함합니다. 이 옵션을 사용하면 검색 인덱스의 크기가 크게 증가합니다", + help_cleanOutputDir: + "출력 디렉터리를 작성하기 전에 TypeDoc이 제거하도록 지정합니다", + help_titleLink: + "헤더의 제목이 가리키는 링크를 설정합니다. 기본값은 문서 홈페이지입니다", + help_navigationLinks: "헤더에 포함될 링크를 정의합니다", + help_sidebarLinks: "사이드바에 포함될 링크를 정의합니다", + help_navigationLeaves: + "확장되지 않아야 할 네비게이션 트리의 가지를 정의합니다", + help_navigation: "네비게이션 사이드바의 구성 방식을 결정합니다", + help_visibilityFilters: + "기본 내장 필터 및 수정자 태그에 대한 기본 가시성을 지정합니다", + help_searchCategoryBoosts: + "선택한 카테고리에 대해 검색에서 중요도 부스트를 구성합니다", + help_searchGroupBoosts: + "선택한 종류(예: '클래스')에 대해 검색에서 중요도 부스트를 구성합니다", + help_jsDocCompatibility: + "JSDoc 주석과 유사성을 높이기 위한 주석 파싱의 호환성 옵션을 설정합니다", + help_commentStyle: "TypeDoc이 주석을 검색하는 방식을 결정합니다", + help_useTsLinkResolution: + "TypeScript 링크 해결을 사용하여 @link 태그가 가리키는 위치를 결정합니다. 이 옵션은 JSDoc 스타일 주석에만 적용됩니다", + help_preserveLinkText: + "링크 텍스트가 없는 @link 태그는 텍스트 내용을 링크로 사용합니다. 설정되지 않으면 대상 리플렉션 이름을 사용합니다", + help_blockTags: "TypeDoc이 주석을 파싱할 때 인식할 블록 태그를 지정합니다", + help_inlineTags: + "TypeDoc이 주석을 파싱할 때 인식할 인라인 태그를 지정합니다", + help_modifierTags: + "TypeDoc이 주석을 파싱할 때 인식할 수정자 태그를 지정합니다", + help_categorizeByGroup: + "카테고리화가 그룹 수준에서 수행될지 여부를 지정합니다", + help_defaultCategory: + "카테고리가 지정되지 않은 리플렉션의 기본 카테고리를 지정합니다", + help_categoryOrder: + "카테고리가 표시될 순서를 지정합니다. *은 리스트에 없는 카테고리의 상대적 순서를 나타냅니다", + help_groupOrder: + "그룹이 표시될 순서를 지정합니다. *은 리스트에 없는 그룹의 상대적 순서를 나타냅니다", + help_sort: "문서화된 값에 대한 정렬 전략을 지정합니다", + help_sortEntryPoints: + "진입점이 다른 리플렉션과 동일한 정렬 규칙을 따를지 여부를 지정합니다", + help_kindSortOrder: + "'kind'가 지정된 경우 리플렉션의 정렬 순서를 지정합니다", + help_watch: "파일 변경을 감지하고 문서를 다시 빌드할지 여부를 지정합니다", + help_preserveWatchOutput: + "TypeDoc이 컴파일 실행 간에 화면을 지우지 않도록 지정합니다", + help_skipErrorChecking: + "TypeScript의 타입 체크를 실행하지 않고 문서를 생성하지 않도록 지정합니다", + help_help: "해당 메시지을 출력합니다", + + help_version: "TypeDoc의 버전을 출력합니다", + help_showConfig: "해결된 구성을 출력하고 종료합니다", + help_plugin: + "로드할 npm 플러그인을 지정합니다. 생략하면 설치된 모든 플러그인이 로드됩니다", + help_logLevel: "사용할 로깅 레벨을 지정합니다", + help_treatWarningsAsErrors: "모든 경고를 오류로 처리합니다", + help_treatValidationWarningsAsErrors: + "검증 중 경고를 오류로 처리합니다. 이 옵션은 검증 경고에 대해 treatWarningsAsErrors를 비활성화할 수 없습니다", + help_intentionallyNotExported: + "'참조되었지만 문서화되지 않았음' 경고를 생성하지 않을 유형의 목록", + help_requiredToBeDocumented: "문서화해야 할 리플렉션 종류의 목록", + help_validation: + "생성된 문서에 대해 TypeDoc이 수행할 검증 단계를 지정합니다", + option_0_must_be_between_1_and_2: "{0}은(는) {1}과(와) {2} 사이어야 합니다", + option_0_must_be_equal_to_or_greater_than_1: + "{0}은(는) {1} 이상이어야 합니다", + option_0_must_be_less_than_or_equal_to_1: "{0}은(는) {1} 이하여야 합니다", + option_0_must_be_one_of_1: "{0}은(는) 다음 중 하나여야 합니다: {1}", + flag_0_is_not_valid_for_1_expected_2: + "플래그 '{0}'은(는) {1}에 대해 유효하지 않습니다. {2} 중 하나가 예상됩니다", + expected_object_with_flag_values_for_0: + "{0}에 대해 플래그 값이 포함된 객체가 예상됩니다. true/false도 사용할 수 있습니다", + flag_values_for_0_must_be_booleans: + "{0}에 대한 플래그 값은 불리언이어야 합니다", + locales_must_be_an_object: + "'locales' 옵션은 'en: { theme_implements: \"Implements\" }'와 비슷한 객체로 설정되어야 합니다", + external_symbol_link_mappings_must_be_object: + "externalSymbolLinkMappings는 Record> 형태여야 합니다", + highlight_theme_0_must_be_one_of_1: + "{0}은(는) 다음 중 하나여야 합니다: {1}", + highlightLanguages_contains_invalid_languages_0: + "highlightLanguages에 유효하지 않은 언어가 포함되어 있습니다: {0}. 지원하는 언어 목록을 확인하려면 typedoc --help를 실행하세요", + hostedBaseUrl_must_start_with_http: + "hostedBaseUrl은 'http://' 또는 'https://'로 시작해야 합니다", + useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl: + "useHostedBaseUrlForAbsoluteLinks 옵션을 사용하려면 hostedBaseUrl이 설정되어 있어야 합니다", + option_0_must_be_an_object: "'{0}' 옵션은 배열이 아닌 객체여야 합니다", + option_0_must_be_a_function: "'{0}' 옵션은 함수여야 합니다", + option_0_values_must_be_numbers: "{0}의 모든 값은 숫자여야 합니다", + option_0_values_must_be_array_of_tags: + "{0}은(는) 유효한 태그 이름 배열이어야 합니다", + + loaded_multiple_times_0: + "TypeDoc가 여러 번 로드되었습니다. 일반적으로 자체적으로 설치된 TypeDoc을 가진 플러그인들이 이를 일으킵니다. 로드된 경로는 다음과 같습니다:\n\t{0}", + unsupported_ts_version_0: + "지원되지 않는 Typescript 버전으로 실행 중입니다! TypeDoc이 충돌이 생기는 경우 이것이 그 이유가 됩니다. TypeDoc {0}을 지원합니다.", + no_compiler_options_set: + "컴파일러 옵션이 설정되지 않았습니다. 이는 TypeDoc이 tsconfig.json을 찾지 못했음을 의미할 수 있습니다. 생성된 문서는 비어 있을 수 있습니다.", + + loaded_plugin_0: `로드된 플러그인 {0}`, + + solution_not_supported_in_watch_mode: + "제공된 tsconfig 파일은 watch 모드에서 지원되지 않는 솔루션 스타일 tsconfig처럼 보입니다.", + strategy_not_supported_in_watch_mode: + "watch 모드에서는 EntryPointStrategy를 확인 또는 확장으로 설정해야 합니다.", + found_0_errors_and_1_warnings: + "{0}개의 오류와 {1}개의 경고를 발견했습니다.", + + docs_could_not_be_generated: "위 오류로 인해 문서를 생성할 수 없습니다.", + // ReflectionFlag translations + flag_private: "Private", + flag_protected: "Protected", + flag_public: "Public", + flag_static: "Static", + flag_external: "External", + flag_optional: "Optional", + flag_rest: "Rest", + flag_abstract: "Abstract", + flag_const: "Const", + flag_readonly: "Readonly", + flag_inherited: "Inherited", + + kind_project: "프로젝트", + kind_module: "모듈", + kind_namespace: "네임스페이스", + kind_enum: "열거형", + kind_enum_member: "포함된 값", + kind_variable: "변수", + kind_function: "함수", + kind_class: "클래스", + kind_interface: "인터페이스", + kind_constructor: "생성자", + kind_property: "속성", + kind_method: "메소드", + kind_call_signature: "호출 시그니쳐", + kind_index_signature: "인덱스 시그니쳐", + kind_constructor_signature: "생성자 시그니쳐", + kind_parameter: "매개변수", + kind_type_literal: "타입 리터럴", + kind_type_parameter: "타입 매개변수", + kind_accessor: "접근자", + kind_get_signature: "get 시그니쳐", + kind_set_signature: "set 시그니쳐", + kind_type_alias: "타입 별칭", + kind_reference: "참조", + kind_document: "문서", + + kind_plural_project: "프로젝트", + kind_plural_module: "모듈", + kind_plural_namespace: "네임스페이스", + kind_plural_enum: "열거형", + kind_plural_enum_member: "포함된 값", + kind_plural_variable: "변수", + kind_plural_function: "함수", + kind_plural_class: "클래스", + kind_plural_interface: "인터페이스", + kind_plural_constructor: "생성자", + kind_plural_property: "속성", + kind_plural_method: "메소드", + kind_plural_call_signature: "호출 시그니쳐", + kind_plural_index_signature: "인덱스 시그니쳐", + kind_plural_constructor_signature: "생성자 시그니쳐", + kind_plural_parameter: "매개변수", + kind_plural_type_literal: "타입 리터럴", + kind_plural_type_parameter: "타입 매개변수", + kind_plural_accessor: "접근자", + kind_plural_get_signature: "get 시그니쳐", + kind_plural_set_signature: "set 시그니쳐", + kind_plural_type_alias: "타입 별칭", + kind_plural_reference: "참조", + kind_plural_document: "문서", + + theme_implements: "구현한 타입", + theme_indexable: "인덱싱 가능", + theme_type_declaration: "타입 선언", + theme_index: "둘러보기", + theme_hierarchy: "계층", + theme_hierarchy_view_full: "전체 보기", + theme_implemented_by: "구현", + theme_defined_in: "정의 위치:", + theme_implementation_of: "구현하는 타입:", + theme_inherited_from: "상속받은 타입:", + theme_overrides: "오버라이드 대상:", + theme_returns: "반환 형식:", + theme_re_exports: "다시 내보내진 원본:", + theme_renames_and_re_exports: "새 이름으로 내보내진 원본:", + theme_generated_using_typedoc: "TypeDoc으로 생성됨", + + theme_preparing_search_index: "검색 색인 준비 중...", + theme_search_index_not_available: "검색 색인을 사용할 수 없습니다.", + + theme_settings: "설정", + theme_member_visibility: "필터", + theme_theme: "테마", + theme_os: "시스템", + theme_light: "라이트", + theme_dark: "다크", + theme_on_this_page: "목차", + + theme_search: "검색", + theme_menu: "메뉴", + theme_permalink: "링크", +}); diff --git a/src/lib/internationalization/locales/zh.cts b/src/lib/internationalization/locales/zh.cts new file mode 100644 index 000000000..4692daf80 --- /dev/null +++ b/src/lib/internationalization/locales/zh.cts @@ -0,0 +1,419 @@ +import { buildIncompleteTranslation } from "../translatable"; + +export = buildIncompleteTranslation({ + loaded_multiple_times_0: + "TypeDoc 已加载多次。这通常是由具有自己的 TypeDoc 安装的插件引起的。加载的路径为:\n{0}", + unsupported_ts_version_0: + "您正在使用不受支持的 TypeScript 版本运行!如果 TypeDoc 崩溃,这就是原因。TypeDoc 支持 {0}", + no_compiler_options_set: + "未设置编译器选项。这可能意味着 TypeDoc 没有找到你的 tsconfig.json。生成的文档可能为空", + loaded_plugin_0: "已加载插件 {0}", + solution_not_supported_in_watch_mode: + "提供的 tsconfig 文件看起来像解决方案样式的 tsconfig,在监视模式下不受支持", + strategy_not_supported_in_watch_mode: + "对于监视模式,entryPointStrategy 必须设置为 resolve 或 expand", + found_0_errors_and_1_warnings: "发现 {0} 个错误和 {1} 个警告", + docs_could_not_be_generated: "由于上述错误,无法生成文档", + docs_generated_at_0: "文档生成于 {0}", + json_written_to_0: "JSON 已写入 {0}", + no_entry_points_for_packages: "没有为包模式提供入口点,无法生成文档", + failed_to_find_packages: + "找不到任何软件包,请确保您至少提供了一个包含 package.json 的目录作为入口点", + nested_packages_unsupported_0: + "位于 {0} 的项目已将 entryPointStrategy 设置为包,但不支持嵌套包", + previous_error_occurred_when_reading_options_for_0: + "读取 {0} 处的包的选项时发生上一个错误", + converting_project_at_0: "正在转换 {0} 处的项目", + failed_to_convert_packages: "无法转换一个或多个包,结果将不会合并在一起", + merging_converted_projects: "合并转换后的项目", + no_entry_points_to_merge: "没有提供合并的入口点", + entrypoint_did_not_match_files_0: "入口点 glob {0} 与任何文件均不匹配", + failed_to_parse_json_0: "无法将 {0} 处的文件解析为 json", + failed_to_read_0_when_processing_document_tag_in_1: + "处理 {1} 中注释的 @document 标记时无法读取文件 {0}", + failed_to_read_0_when_processing_project_document: + "添加项目文档时无法读取文件 {0}", + failed_to_read_0_when_processing_document_child_in_1: + "处理 {1} 中的文档子项时无法读取文件 {0}", + frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values: + "{0} 中的 Frontmatter 子项应为字符串数组或具有字符串值的对象", + converting_union_as_interface: + "在联合类型上使用 @interface 将丢弃联合所有分支上不存在的属性。TypeDoc 的输出可能无法准确描述您的源代码", + converting_0_as_class_requires_value_declaration: + "将 {0} 转换为类需要表示非类型值的声明", + converting_0_as_class_without_construct_signatures: + "{0} 正在转换为类,但没有任何构造签名", + comment_for_0_should_not_contain_block_or_modifier_tags: + "{0} 的注释不应包含任何块或修饰符标签", + symbol_0_has_multiple_declarations_with_comment: + "{0} 有多个带注释的声明。将使用任意注释", + comments_for_0_are_declared_at_1: "{0} 的注释声明于:\n{1}", + multiple_type_parameters_on_template_tag_unsupported: + "TypeDoc 不支持在带有注释的单个 @template 标记中定义多个类型参数", + failed_to_find_jsdoc_tag_for_name_0: + "解析注释后无法找到 {0} 的 JSDoc 标签,请提交错误报告", + // relative_path_0_is_not_a_file_and_will_not_be_copied_to_output + inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0: + "内联 @inheritDoc 标记不应出现在块标记内,因为它不会在 {0} 处的注释中被处理。", + at_most_one_remarks_tag_expected_in_comment_at_0: + "注释中最多应有一个 @remarks 标签,忽略 {0} 处注释中除第一个标签之外的所有标签", + at_most_one_returns_tag_expected_in_comment_at_0: + "注释中最多应有一个 @returns 标签,忽略 {0} 处注释中除第一个标签之外的所有标签", + at_most_one_inheritdoc_tag_expected_in_comment_at_0: + "注释中最多应有一个 @inheritDoc 标签,忽略 {0} 处注释中除第一个标签之外的所有标签", + content_in_summary_overwritten_by_inheritdoc_in_comment_at_0: + "摘要部分的内容将被 {0} 处注释中的 @inheritDoc 标记覆盖", + content_in_remarks_block_overwritten_by_inheritdoc_in_comment_at_0: + "@remarks 块中的内容将被 {0} 处注释中的 @inheritDoc 标记覆盖", + example_tag_literal_name: + "示例标签的第一行将按字面意思理解为示例名称,并且只能包含文本", + inheritdoc_tag_properly_capitalized: "@inheritDoc 标签应正确大写", + treating_unrecognized_tag_0_as_modifier: + "将无法识别的标签 {0} 视为修饰标签", + unmatched_closing_brace: "不匹配的右括号", + unescaped_open_brace_without_inline_tag: "遇到未转义的无内联标签的开括号", + unknown_block_tag_0: "遇到未知的区块标记 {0}", + unknown_inline_tag_0: "遇到未知的内联标记 {0}", + open_brace_within_inline_tag: "在内联标签中遇到左括号,这可能是一个错误", + inline_tag_not_closed: "内联标签未关闭", + failed_to_resolve_link_to_0_in_comment_for_1: + "无法解析“{1}”评论中的“{0}”链接", + type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs: + "{0} 在 {1} 中定义,被 {2} 引用,但未包含在文档中", + reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: + "{0} ({1}),在 {2} 中定义,没有任何文档", + invalid_intentionally_not_exported_symbols_0: + "以下符号被标记为有意不导出,但未在文档中引用,或已被导出:\n{0}", + not_all_search_category_boosts_used_0: + "文档中并未使用 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}”的 @categoryDe​​scription,但该类别中没有子项", + comment_for_0_includes_groupDescription_for_1_but_no_child_in_group: + "对 {0} 的评论包含“{1}”的 @groupDescription,但该组中没有子项", + 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} 互斥", + signature_0_has_unused_param_with_name_1: + "签名 {0} 有一个名为“{1}”的 @param,但未被使用", + declaration_reference_in_inheritdoc_for_0_not_fully_parsed: + "@inheritDoc 中对 {0} 的声明引用未完全解析,可能会解析不正确", + failed_to_find_0_to_inherit_comment_from_in_1: + "在 {1} 的评论中找不到要继承的评论“{0}”", + failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2: `无法解析 {1} 的注释中指向"{0}"的链接。您可能想要"{2}"`, + failed_to_resolve_link_to_0_in_readme_for_1: `无法解析 {1} 的自述文件中指向"{0}"的链接`, + failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2: `无法解析 {1} 的自述文件中指向"{0}"的链接。您可能想要"{2}"`, + reflection_0_tried_to_copy_comment_from_1_but_source_had_no_comment: + "{0} 尝试使用 @inheritDoc 从 {1} 复制注释,但源没有相关注释", + inheritdoc_circular_inheritance_chain_0: "@inheritDoc 指定循环继承链:{0}", + provided_readme_at_0_could_not_be_read: "提供的 README 路径无法读取 {0}", + defaulting_project_name: + "未指定 --name 选项,并且未找到 package.json。将项目名称默认为“Documentation”", + disable_git_set_but_not_source_link_template: + "已设置 disableGit,但未设置 sourceLinkTemplate,因此无法生成源链接。设置 sourceLinkTemplate 或 disableSources 以阻止源跟踪", + disable_git_set_and_git_revision_used: + "disableGit 已设置,并且 sourceLinkTemplate 包含 {gitRevision},由于未提供修订,因此将替换为空字符串", + git_remote_0_not_valid: "提供的 git 远程“{0}”无效。源链接将失效", + custom_css_file_0_does_not_exist: "{0} 处的自定义 CSS 文件不存在", + unsupported_highlight_language_0_not_highlighted_in_comment_for_1: + "不支持的突出显示语言 {0} 将不会在 {1} 的评论中突出显示", + unloaded_language_0_not_highlighted_in_comment_for_1: + "语言为 {0} 的代码块将不会在 {1} 的注释中突出显示,因为它未包含在 highlightLanguages 选项中", + yaml_frontmatter_not_an_object: "预期 YAML 前置内容为对象", + could_not_write_0: "无法写入 {0}", + could_not_empty_output_directory_0: "无法清空输出目录 {0}", + could_not_create_output_directory_0: "无法创建输出目录 {0}", + theme_0_is_not_defined_available_are_1: "主题“{0}”未定义。可用主题为:{1}", + custom_theme_does_not_define_getSlugger: + "自定义主题没有定义 getSlugger(reflection) 方法,但尝试渲染 markdown", + no_entry_points_provided: "没有提供入口点,这可能是配置错误", + unable_to_find_any_entry_points: "无法找到任何入口点。请参阅先前的警告", + watch_does_not_support_packages_mode: "监视模式不支持“包”样式的入口点", + watch_does_not_support_merge_mode: "监视模式不支持“合并”样式的入口点", + entry_point_0_not_in_program: + "入口点 {0} 未被 tsconfig 中的“files”或“include”选项引用", + use_expand_or_glob_for_files_in_dir: + "如果要包含此目录中的文件,请设置 --entryPointStrategy 以展开或指定 glob", + glob_0_did_not_match_any_files: "glob {0} 与任何文件均不匹配", + 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 的目录不匹配", + 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 + 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}”选项只能通过配置文件指定", + option_0_expected_a_value_but_none_provided: + "--{0} 需要一个值,但没有给出任何参数", + unknown_option_0_may_have_meant_1: "未知选项:{0},你可能指的是:\n{1}", + typedoc_key_in_0_ignored: + "{0} 中的“typedoc”键已被旧包 entryPointStrategy 使用,将被忽略", + typedoc_options_must_be_object_in_0: + "无法解析 {0} 中的“typedocOptions”字段,请确保它存在且包含对象", + tsconfig_file_0_does_not_exist: "tsconfig 文件 {0} 不存在", + tsconfig_file_specifies_options_file: + "tsconfig 文件中的“typedocOptions”指定要读取的选项文件,但该选项文件已被读取。这可能是配置错误", + tsconfig_file_specifies_tsconfig_file: + "tsconfig 文件中的“typedocOptions”可能未指定要读取的 tsconfig 文件", + tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json: + "typedoc.json 中定义的 {0} 将被 tsdoc.json 中的配置覆盖", + failed_read_tsdoc_json_0: "无法读取位于 {0} 的 tsdoc.json 文件", + invalid_tsdoc_json_0: "文件 {0} 不是有效的 tsdoc.json 文件", + options_file_0_does_not_exist: "选项文件 {0} 不存在", + failed_read_options_file_0: "无法解析 {0},请确保其存在并导出对象", + invalid_plugin_0_missing_load_function: + "插件 {0} 中的结构无效,未找到加载函数", + plugin_0_could_not_be_loaded: "无法加载插件 {0}", + help_options: + "指定应加载的 json 选项文件。如果未指定,TypeDoc 将在当前目录中查找“typedoc.json”", + help_tsconfig: + "指定应加载的 TypeScript 配置文件。如果未指定,TypeDoc 将在当前目录中查找“tsconfig.json”", + help_compilerOptions: "有选择地覆盖 TypeDoc 使用的 TypeScript 编译器选项", + help_lang: "设置生成和 TypeDoc 消息中使用的语言", + help_locales: + "为指定语言环境添加翻译。此选项主要用作在等待官方语言环境支持添加到 TypeDoc 时的权宜之计", + help_packageOptions: + "当 entryPointStrategy 设置为包时,设置将在每个包中设置的选项", + help_entryPoints: "文档的入口点", + help_entryPointStrategy: "将入口点转换为文档模块所采用的策略", + help_alwaysCreateEntryPointModule: + "设置后,TypeDoc 将始终为入口点创建一个“模块”,即使只提供了一个", + help_projectDocuments: + "应作为子项添加到生成文档根目录中的文档。支持使用 glob 匹配多个文件", + help_exclude: "定义在扩展指定为入口点的目录时要排除的模式", + help_externalPattern: "定义应被视为外部的文件的模式", + help_excludeExternals: "防止记录外部解析的符号", + help_excludeNotDocumented: "防止未明确记录的符号出现在结果中", + help_excludeNotDocumentedKinds: + "指定可以通过 excludeNotDocumented 删除的反射类型", + help_excludeInternal: "防止标有 @internal 的符号被记录", + help_excludeCategories: "从文档中排除此类别中的符号", + help_excludePrivate: "忽略私有变量和方法,默认为 true。", + help_excludeProtected: "忽略受保护的变量和方法", + help_excludeReferences: + "如果一个符号被导出多次,则忽略除第一次导出之外的所有导出", + help_externalSymbolLinkMappings: "为文档中未包含的符号定义自定义链接", + help_out: "指定文档应写入的位置", + help_json: "指定描述项目的 JSON 文件写入的位置和文件名", + help_pretty: "指定输出 JSON 是否应使用制表符进行格式化", + help_emit: "指定 TypeDoc 应发出的内容,“docs”、“both”或“none”", + help_theme: "指定用于呈现文档的主题名称", + help_lightHighlightTheme: "指定浅色模式下的代码高亮主题", + help_darkHighlightTheme: "指定暗黑模式下的代码高亮主题", + help_highlightLanguages: "指定渲染时将加载哪些语言来突出显示代码", + help_customCss: "要导入主题的自定义 CSS 文件的路径", + help_markdownItOptions: + "指定传递给 markdown-it(TypeDoc 使用的 Markdown 解析器)的选项", + help_markdownItLoader: + "指定加载 markdown-it 实例时要调用的回调。将传递 TypeDoc 将使用的解析器实例", + help_maxTypeConversionDepth: "设置要转换类型的最大深度", + help_name: "设置将在模板标题中使用的项目名称", + help_includeVersion: "将软件包版本添加到项目名称中", + help_disableSources: "记录反射时禁用设置反射源", + help_sourceLinkTemplate: + "指定生成源 URL 时要使用的链接模板。如果未设置,将使用 git remote 自动创建。支持 {path}、{line}、{gitRevision} 占位符", + help_gitRevision: + "使用指定修订版本而不是最新修订版本来链接到 GitHub/Bitbucket 源文件。如果设置了 disableSources,则无效", + help_gitRemote: + "使用指定的远程链接到 GitHub/Bitbucket 源文件。如果设置了 disableGit 或 disableSources,则无效", + help_disableGit: + "假设所有内容都可以通过 sourceLinkTemplate 进行链接,如果启用此功能,则必须设置 sourceLinkTemplate。{path} 将以 basePath 为根", + help_basePath: "指定显示文件路径时使用的基本路径", + help_excludeTags: "从文档注释中删除列出的块/修饰符标签", + help_readme: + "应显示在索引页上的自述文件路径。传递“none”以禁用索引页并在全局页上启动文档", + help_cname: "设置 CNAME 文件文本,这对于 GitHub Pages 上的自定义域很有用", + help_sourceLinkExternal: "指定源链接应被视为外部链接,并在新选项卡中打开", + help_githubPages: + "生成 .nojekyll 文件以防止 GitHub Pages 中出现 404 错误。默认为“true”", + help_hostedBaseUrl: + "指定用于在我们的输出文件夹和规范链接中生成 sitemap.xml 的基本 URL。如果未指定,则不会生成站点地图", + help_useHostedBaseUrlForAbsoluteLinks: + "如果设置,TypeDoc 将使用 hostingBaseUrl 选项生成到您网站页面的绝对链接", + help_hideGenerator: "不要打印页面末尾的 TypeDoc 链接", + help_customFooterHtml: "TypeDoc 链接后的自定义页脚", + help_customFooterHtmlDisableWrapper: + "如果设置,则禁用 customFooterHtml 的包装元素", + help_hideParameterTypesInTitle: "隐藏签名标题中的参数类型,以便于扫描", + help_cacheBust: "在静态资产链接中包含生成时间", + help_searchInComments: + "如果设置,搜索索引还将包括评论。这将大大增加搜索索引的大小", + help_searchInDocuments: + "如果设置,搜索索引还将包含文档。这将大大增加搜索索引的大小", + help_cleanOutputDir: "如果设置,TypeDoc 将在写入输出之前删除输出目录", + help_titleLink: "设置页眉中的标题指向的链接。默认为文档主页", + help_navigationLinks: "定义要包含在标题中的链接", + help_sidebarLinks: "定义要包含在侧边栏中的链接", + help_navigationLeaves: "导航树中不应扩展的分支", + help_navigation: "确定导航侧边栏的组织方式", + help_visibilityFilters: + "根据修饰符标签指定内置过滤器和附加过滤器的默认可见性", + help_searchCategoryBoosts: "配置搜索以提高所选类别的相关性", + help_searchGroupBoosts: "配置搜索以增强所选种类(例如“类别”)的相关性", + help_jsDocCompatibility: + "设置注释解析的兼容性选项,以增加与 JSDoc 注释的相似度", + help_commentStyle: "确定 TypeDoc 如何搜索注释", + help_useTsLinkResolution: + "使用 TypeScript 的链接解析来确定 @link 标签指向的位置。这仅适用于 JSDoc 样式注释", + help_preserveLinkText: + "如果设置,不带链接文本的 @link 标签将使用文本内容作为链接。如果未设置,将使用目标反射名称", + help_blockTags: "TypeDoc 在解析注释时应该识别的块标签", + help_inlineTags: "TypeDoc 在解析注释时应该识别的内联标签", + help_modifierTags: "TypeDoc 在解析注释时应该识别的修饰符标签", + help_categorizeByGroup: "指定是否在组级别进行分类", + help_defaultCategory: "为没有类别的反射指定默认类别", + help_categoryOrder: "指定类别出现的顺序。* 表示不在列表中的类别的相对顺序", + help_groupOrder: "指定组的显示顺序。* 表示不在列表中的组的相对顺序", + help_sort: "指定记录值的排序策略", + help_sortEntryPoints: "如果设置,入口点将遵循与其他反射相同的排序规则", + help_kindSortOrder: "当指定“种类”时指定反射的排序顺序", + help_watch: "监视文件的变化并在发生更改时重建文档", + help_preserveWatchOutput: "如果设置,TypeDoc 将不会在编译运行之间清除屏幕", + help_skipErrorChecking: "在生成文档之前不要运行 TypeScript 的类型检查", + help_help: "打印此消息", + help_version: "打印 TypeDoc 的版本", + help_showConfig: "打印解析后的配置并退出", + help_plugin: "指定应加载的 npm 插件。省略则加载所有已安装的插件", + help_logLevel: "指定应使用什么级别的日志记录", + help_treatWarningsAsErrors: "如果设置,所有警告都将被视为错误", + help_treatValidationWarningsAsErrors: + "如果设置,验证期间发出的警告将被视为错误。此选项不能用于禁用验证警告的 treatWarningsAsErrors", + help_intentionallyNotExported: "不应产生“引用但未记录”警告的类型列表", + help_requiredToBeDocumented: "必须记录的反射类型列表", + help_validation: "指定 TypeDoc 应对生成的文档执行哪些验证步骤", + unknown_option_0_you_may_have_meant_1: "未知选项“{0}” 你可能指的是:\n{1}", + option_0_must_be_between_1_and_2: "{0} 必须介于 {1} 和 {2} 之间", + option_0_must_be_equal_to_or_greater_than_1: "{0} 必须等于或大于 {1}", + option_0_must_be_less_than_or_equal_to_1: "{0} 必须小于或等于 {1}", + option_0_must_be_one_of_1: "{0} 必须是 {1} 之一", + flag_0_is_not_valid_for_1_expected_2: "标志“{0}”对 {1} 无效,应为 {2} 之一", + expected_object_with_flag_values_for_0: + "预期为一个带有标志值为 {0} 或 true/false 的对象", + flag_values_for_0_must_be_booleans: "{0} 的标志值必须是布尔值", + locales_must_be_an_object: + "'locales' 选项必须设置为类似于以下对象:{ en: { theme_implements: \"Implements\" }}", + exclude_not_documented_specified_0_valid_values_are_1: + "excludeNotDocumentedKinds 只能指定已知值,并且提供了无效值 ({0})。有效类型为:\n{1}", + external_symbol_link_mappings_must_be_object: + "externalSymbolLinkMappings 必须是 Record>", + highlight_theme_0_must_be_one_of_1: "{0} 必须是下列之一:{1}", + highlightLanguages_contains_invalid_languages_0: + "highlightLanguages 包含无效语言:{0},运行 typedoc --help 获取受支持语言的列表", + hostedBaseUrl_must_start_with_http: + "hostingBaseUrl 必须以 http:// 或 https:// 开头", + useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl: + "useHostedBaseUrlForAbsoluteLinks 选项要求设置 hostingBaseUrl", + option_0_must_be_an_object: "“{0}”选项必须是非数组对象", + option_0_must_be_a_function: "‘{0}’ 选项必须是一个函数", + option_0_must_be_object_with_urls: + "{0} 必须是具有字符串标签作为键和 URL 值的对象", + visibility_filters_only_include_0: + "visibilityFilters 只能包含以下非@键:{0}", + visibility_filters_must_be_booleans: + "visibilityFilters 的所有值都必须是布尔值", + option_0_values_must_be_numbers: "{0} 的所有值都必须是数字", + option_0_values_must_be_array_of_tags: "{0} 必须是有效标签名称的数组", + option_0_specified_1_but_only_2_is_valid: + "{0} 只能指定已知值,并且提供了无效值 ({1})。有效的排序策略为:\n{2}", + kind_project: "项目", + kind_module: "模块", + kind_namespace: "命名空间", + kind_enum: "枚举", + kind_enum_member: "枚举成员", + kind_variable: "变量", + kind_function: "函数", + kind_class: "类", + kind_interface: "接口", + kind_constructor: "构造函数", + kind_property: "属性", + kind_method: "方法", + kind_call_signature: "调用签名", + kind_index_signature: "索引签名", + kind_constructor_signature: "构造函数签名", + kind_parameter: "范围", + kind_type_literal: "类型文字", + kind_type_parameter: "类型参数", + kind_accessor: "访问器", + kind_get_signature: "获取签名", + kind_set_signature: "设置签名", + kind_type_alias: "类型别名", + kind_reference: "参考", + kind_document: "文档", + kind_plural_project: "项目", + kind_plural_module: "模块", + kind_plural_namespace: "命名空间", + kind_plural_enum: "枚举", + kind_plural_enum_member: "枚举成员", + kind_plural_variable: "变量", + kind_plural_function: "功能", + kind_plural_class: "类", + kind_plural_interface: "接口", + kind_plural_constructor: "构造函数", + kind_plural_property: "特性", + kind_plural_method: "方法", + kind_plural_call_signature: "呼叫签名", + kind_plural_index_signature: "索引签名", + kind_plural_constructor_signature: "构造函数签名", + kind_plural_parameter: "参数", + kind_plural_type_literal: "类型文字", + kind_plural_type_parameter: "类型参数", + kind_plural_accessor: "访问器", + kind_plural_get_signature: "获取签名", + kind_plural_set_signature: "设置签名", + kind_plural_type_alias: "类型别名", + kind_plural_reference: "参考", + kind_plural_document: "文档", + flag_protected: "受保护", + flag_private: "私有", + flag_external: "外部", + flag_inherited: "继承", + flag_public: "公开", + flag_static: "静态", + flag_optional: "可选", + flag_rest: "动态参数", + flag_abstract: "抽象", + flag_const: "常量", + flag_readonly: "只读", + theme_implements: "实现", + theme_indexable: "可索引", + theme_type_declaration: "类型声明", + theme_index: "索引", + theme_hierarchy: "层级", + theme_hierarchy_view_full: "查看完整内容", + theme_implemented_by: "实现于", + theme_defined_in: "定义于", + theme_implementation_of: "实现了", + theme_inherited_from: "继承自", + theme_overrides: "覆写了", + theme_returns: "返回", + theme_re_exports: "重新导出", + theme_renames_and_re_exports: "重命名并重新导出", + theme_generated_using_typedoc: "使用 TypeDoc 生成", + theme_preparing_search_index: "正在准备搜索索引...", + theme_search_index_not_available: "搜索索引不可用", + theme_settings: "显示设置", + theme_member_visibility: "成员可见性", + theme_theme: "配色", + theme_os: "自动", + theme_light: "浅色", + theme_dark: "深色", + theme_on_this_page: "目录", + theme_search: "搜索", + theme_menu: "菜单", + theme_permalink: "永久链接", + tag_see: "参阅", + tag_group: "所属分组", + tag_example: "示例", + theme_copy: "复制", + theme_copied: "已复制!", + theme_normally_hidden: "由于您的过滤器设置,该成员已被隐藏。", + theme_class_hierarchy_title: "类继承图表", + theme_loading: "加载中……", +}); diff --git a/src/lib/internationalization/translatable.ts b/src/lib/internationalization/translatable.ts new file mode 100644 index 000000000..67fb8fd72 --- /dev/null +++ b/src/lib/internationalization/translatable.ts @@ -0,0 +1,561 @@ +import type { + blockTags, + inlineTags, + modifierTags, +} from "../utils/options/tsdoc-defaults"; + +export function buildTranslation( + translations: BuiltinTranslatableStringConstraints, +) { + return translations; +} + +export function buildIncompleteTranslation( + translations: Partial, +) { + return translations; +} + +export const translatable = { + loaded_multiple_times_0: + "TypeDoc has been loaded multiple times. This is commonly caused by plugins which have their own installation of TypeDoc. The loaded paths are:\n\t{0}", + unsupported_ts_version_0: + "You are running with an unsupported TypeScript version! If TypeDoc crashes, this is why. TypeDoc supports {0}", + no_compiler_options_set: + "No compiler options set. This likely means that TypeDoc did not find your tsconfig.json. Generated documentation will probably be empty", + + loaded_plugin_0: `Loaded plugin {0}`, + + solution_not_supported_in_watch_mode: + "The provided tsconfig file looks like a solution style tsconfig, which is not supported in watch mode", + strategy_not_supported_in_watch_mode: + "entryPointStrategy must be set to either resolve or expand for watch mode", + found_0_errors_and_1_warnings: "Found {0} errors and {1} warnings", + + docs_could_not_be_generated: + "Documentation could not be generated due to the errors above", + docs_generated_at_0: "Documentation generated at {0}", + json_written_to_0: "JSON written to {0}", + + no_entry_points_for_packages: + "No entry points provided to packages mode, documentation cannot be generated", + failed_to_find_packages: + "Failed to find any packages, ensure you have provided at least one directory as an entry point containing package.json", + nested_packages_unsupported_0: + "Project at {0} has entryPointStrategy set to packages, but nested packages are not supported", + previous_error_occurred_when_reading_options_for_0: + "The previous error occurred when reading options for the package at {0}", + converting_project_at_0: "Converting project at {0}", + failed_to_convert_packages: + "Failed to convert one or more packages, result will not be merged together", + merging_converted_projects: "Merging converted projects", + + no_entry_points_to_merge: "No entry points provided to merge", + entrypoint_did_not_match_files_0: + "The entrypoint glob {0} did not match any files", + failed_to_parse_json_0: `Failed to parse file at {0} as json`, + + failed_to_read_0_when_processing_document_tag_in_1: `Failed to read file {0} when processing @document tag for comment in {1}`, + failed_to_read_0_when_processing_project_document: `Failed to read file {0} when adding project document`, + failed_to_read_0_when_processing_document_child_in_1: `Failed to read file {0} when processing document children in {1}`, + frontmatter_children_0_should_be_an_array_of_strings_or_object_with_string_values: + "Frontmatter children in {0} should be an array of strings or an object with string values", + converting_union_as_interface: `Using @interface on a union type will discard properties not present on all branches of the union. TypeDoc's output may not accurately describe your source code`, + converting_0_as_class_requires_value_declaration: `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`, + + comment_for_0_should_not_contain_block_or_modifier_tags: `The comment for {0} should not contain any block or modifier tags`, + + symbol_0_has_multiple_declarations_with_comment: `{0} has multiple declarations with a comment. An arbitrary comment will be used`, + comments_for_0_are_declared_at_1: `The comments for {0} are declared at:\n\t{1}`, + + // comments/parser.ts + multiple_type_parameters_on_template_tag_unsupported: `TypeDoc does not support multiple type parameters defined in a single @template tag with a comment`, + failed_to_find_jsdoc_tag_for_name_0: `Failed to find JSDoc tag for {0} after parsing comment, please file a bug report`, + relative_path_0_is_not_a_file_and_will_not_be_copied_to_output: `The relative path {0} is not a file and will not be copied to the output directory`, + + inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0: + "An inline @inheritDoc tag should not appear within a block tag as it will not be processed in comment at {0}", + at_most_one_remarks_tag_expected_in_comment_at_0: + "At most one @remarks tag is expected in a comment, ignoring all but the first in comment at {0}", + at_most_one_returns_tag_expected_in_comment_at_0: + "At most one @returns tag is expected in a comment, ignoring all but the first in comment at {0}", + at_most_one_inheritdoc_tag_expected_in_comment_at_0: + "At most one @inheritDoc tag is expected in a comment, ignoring all but the first in comment at {0}", + content_in_summary_overwritten_by_inheritdoc_in_comment_at_0: + "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}", + 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", + treating_unrecognized_tag_0_as_modifier: `Treating unrecognized tag {0} as a modifier tag`, + unmatched_closing_brace: `Unmatched closing brace`, + unescaped_open_brace_without_inline_tag: `Encountered an unescaped open brace without an inline tag`, + unknown_block_tag_0: `Encountered an unknown block tag {0}`, + unknown_inline_tag_0: `Encountered an unknown inline tag {0}`, + open_brace_within_inline_tag: `Encountered an open brace within an inline tag, this is likely a mistake`, + inline_tag_not_closed: `Inline tag is not closed`, + + // validation + 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}"`, + failed_to_resolve_link_to_0_in_readme_for_1: `Failed to resolve link to "{0}" in readme for {1}`, + failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2: `Failed to resolve link to "{0}" in readme for {1}. You may have wanted "{2}"`, + type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs: `{0}, defined in {1}, is referenced by {2} but not included in the documentation`, + reflection_0_kind_1_defined_in_2_does_not_have_any_documentation: `{0} ({1}), defined in {2}, does not have any documentation`, + invalid_intentionally_not_exported_symbols_0: + "The following symbols were marked as intentionally not exported, but were either not referenced in the documentation, or were exported:\n\t{0}", + + // conversion plugins + not_all_search_category_boosts_used_0: `Not all categories specified in searchCategoryBoosts were used in the documentation. The unused categories were:\n\t{0}`, + not_all_search_group_boosts_used_0: `Not all groups specified in searchGroupBoosts were used in the documentation. The unused groups were:\n\t{0}`, + comment_for_0_includes_categoryDescription_for_1_but_no_child_in_group: `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`, + 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: `The modifier tag {0} is mutually exclusive with {1} in the comment for {2}`, + signature_0_has_unused_param_with_name_1: `The signature {0} has an @param with name "{1}", which was not used`, + declaration_reference_in_inheritdoc_for_0_not_fully_parsed: `Declaration reference in @inheritDoc for {0} was not fully parsed and may resolve incorrectly`, + failed_to_find_0_to_inherit_comment_from_in_1: `Failed to find "{0}" to inherit the comment from in the comment for {1}`, + reflection_0_tried_to_copy_comment_from_1_but_source_had_no_comment: `{0} tried to copy a comment from {1} with @inheritDoc, but the source has no associated comment`, + inheritdoc_circular_inheritance_chain_0: `@inheritDoc specifies a circular inheritance chain: {0}`, + provided_readme_at_0_could_not_be_read: `Provided README path, {0} could not be read`, + defaulting_project_name: + 'The --name option was not specified, and no package.json was found. Defaulting project name to "Documentation"', + disable_git_set_but_not_source_link_template: `disableGit is set, but sourceLinkTemplate is not, so source links cannot be produced. Set a sourceLinkTemplate or disableSources to prevent source tracking`, + disable_git_set_and_git_revision_used: `disableGit is set and sourceLinkTemplate contains {gitRevision}, which will be replaced with an empty string as no revision was provided`, + git_remote_0_not_valid: `The provided git remote "{0}" was not valid. Source links will be broken`, + + // output plugins + custom_css_file_0_does_not_exist: `Custom CSS file at {0} does not exist`, + unsupported_highlight_language_0_not_highlighted_in_comment_for_1: `Unsupported highlight language {0} will not be highlighted in comment for {1}`, + unloaded_language_0_not_highlighted_in_comment_for_1: `Code block with language {0} will not be highlighted in comment for {1} as it was not included in the highlightLanguages option`, + yaml_frontmatter_not_an_object: `Expected YAML frontmatter to be an object`, + + // renderer + could_not_write_0: `Could not write {0}`, + could_not_empty_output_directory_0: `Could not empty the output directory {0}`, + could_not_create_output_directory_0: `Could not create the output directory {0}`, + theme_0_is_not_defined_available_are_1: `The theme '{0}' is not defined. The available themes are: {1}`, + custom_theme_does_not_define_getSlugger: `Custom theme does not define a getSlugger(reflection) method, but tries to render markdown`, + + // entry points + no_entry_points_provided: + "No entry points were provided, this is likely a misconfiguration", + unable_to_find_any_entry_points: + "Unable to find any entry points. See previous warnings", + watch_does_not_support_packages_mode: + "Watch mode does not support 'packages' style entry points", + watch_does_not_support_merge_mode: + "Watch mode does not support 'merge' style entry points", + entry_point_0_not_in_program: `The entry point {0} is not referenced by the 'files' or 'include' option in your tsconfig`, + 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`, + 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`, + entry_point_0_did_not_match_any_packages: `The entry point glob {0} did not match any directories containing package.json`, + file_0_not_an_object: `The file {0} is not an object`, + + // 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: `Serialized project referenced {0}, which does not exist or is not a file 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}`, + + 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}`, + + typedoc_key_in_0_ignored: `The 'typedoc' key in {0} was used by the legacy-packages entryPointStrategy and will be ignored`, + typedoc_options_must_be_object_in_0: `Failed to parse the "typedocOptions" field in {0}, ensure it exists and contains an object`, + tsconfig_file_0_does_not_exist: `The tsconfig file {0} does not exist`, + tsconfig_file_specifies_options_file: `"typedocOptions" in tsconfig file specifies an option file to read but the option file has already been read. This is likely a misconfiguration`, + tsconfig_file_specifies_tsconfig_file: `"typedocOptions" in tsconfig file may not specify a tsconfig file to read`, + tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json: `The {0} defined in typedoc.json will be overwritten by configuration in tsdoc.json`, + failed_read_tsdoc_json_0: `Failed to read tsdoc.json file at {0}`, + invalid_tsdoc_json_0: `The file {0} is not a valid tsdoc.json file`, + + 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`, + + // plugins + invalid_plugin_0_missing_load_function: `Invalid structure in plugin {0}, no load function found`, + plugin_0_could_not_be_loaded: `The plugin {0} could not be loaded`, + + // option declarations help + help_options: + "Specify a json option file that should be loaded. If not specified TypeDoc will look for 'typedoc.json' in the current directory", + help_tsconfig: + "Specify a TypeScript config file that should be loaded. If not specified TypeDoc will look for 'tsconfig.json' in the current directory", + help_compilerOptions: + "Selectively override the TypeScript compiler options used by TypeDoc", + help_lang: + "Sets the language to be used in generation and in TypeDoc's messages", + help_locales: + "Add translations for a specified locale. This option is primarily intended to be used as a stopgap while waiting for official locale support to be added to TypeDoc", + help_packageOptions: + "Set options which will be set within each package when entryPointStrategy is set to packages", + + help_entryPoints: "The entry points of your documentation", + help_entryPointStrategy: + "The strategy to be used to convert entry points into documentation modules", + help_alwaysCreateEntryPointModule: + "When set, TypeDoc will always create a `Module` for entry points, even if only one is provided", + help_projectDocuments: + "Documents which should be added as children to the root of the generated documentation. Supports globs to match multiple files", + help_exclude: + "Define patterns to be excluded when expanding a directory that was specified as an entry point", + help_externalPattern: + "Define patterns for files that should be considered being external", + help_excludeExternals: + "Prevent externally resolved symbols from being documented", + help_excludeNotDocumented: + "Prevent symbols that are not explicitly documented from appearing in the results", + 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_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", + help_out: "Specify the location the documentation should be written to", + help_json: + "Specify the location and filename a JSON file describing the project is written to", + help_pretty: + "Specify whether the output JSON should be formatted with tabs", + help_emit: "Specify what TypeDoc should emit, 'docs', 'both', or 'none'", + help_theme: "Specify the theme name to render the documentation with", + help_lightHighlightTheme: + "Specify the code highlighting theme in light mode", + help_darkHighlightTheme: "Specify the code highlighting theme in dark mode", + help_highlightLanguages: + "Specify the languages which will be loaded to highlight code when rendering", + help_customCss: "Path to a custom CSS file to for the theme to import", + help_markdownItOptions: + "Specify the options passed to markdown-it, the Markdown parser used by TypeDoc", + help_markdownItLoader: + "Specify a callback to be called when loading the markdown-it instance. Will be passed the instance of the parser which TypeDoc will use", + help_maxTypeConversionDepth: + "Set the maximum depth of types to be converted", + help_name: + "Set the name of the project that will be used in the header of the template", + help_includeVersion: "Add the package version to the project name", + help_disableSources: + "Disable setting the source of a reflection when documenting it", + help_sourceLinkTemplate: + "Specify a link template to be used when generating source urls. If not set, will be automatically created using the git remote. Supports {path}, {line}, {gitRevision} placeholders", + help_gitRevision: + "Use specified revision instead of the last revision for linking to GitHub/Bitbucket source files. Has no effect if disableSources is set", + help_gitRemote: + "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_excludeTags: "Remove the listed block/modifier tags from doc comments", + 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_cname: + "Set the CNAME file text, it's useful for custom domains on GitHub Pages", + help_sourceLinkExternal: + "Specifies that source links should be treated as external links to be opened in a new tab", + help_githubPages: + "Generate a .nojekyll file to prevent 404 errors in GitHub Pages. Defaults to `true`", + help_hostedBaseUrl: + "Specify a base URL to be used in generating a sitemap.xml in our output folder and canonical links. If not specified, no sitemap will be generated", + help_useHostedBaseUrlForAbsoluteLinks: + "If set, TypeDoc will produce absolute links to pages on your site using the hostedBaseUrl option", + help_hideGenerator: "Do not print the TypeDoc link at the end of the page", + help_customFooterHtml: "Custom footer after the TypeDoc link", + help_customFooterHtmlDisableWrapper: + "If set, disables the wrapper element for customFooterHtml", + help_hideParameterTypesInTitle: + "Hides parameter types in signature titles for easier scanning", + help_cacheBust: "Include the generation time in links to static assets", + help_searchInComments: + "If set, the search index will also include comments. This will greatly increase the size of the search index", + help_searchInDocuments: + "If set, the search index will also include documents. This will greatly increase the size of the search index", + help_cleanOutputDir: + "If set, TypeDoc will remove the output directory before writing output", + help_titleLink: + "Set the link the title in the header points to. Defaults to the documentation homepage", + help_navigationLinks: "Defines links to be included in the header", + help_sidebarLinks: "Defines links to be included in the sidebar", + help_navigationLeaves: + "Branches of the navigation tree which should not be expanded", + help_navigation: "Determines how the navigation sidebar is organized", + help_visibilityFilters: + "Specify the default visibility for builtin filters and additional filters according to modifier tags", + help_searchCategoryBoosts: + "Configure search to give a relevance boost to selected categories", + help_searchGroupBoosts: + 'Configure search to give a relevance boost to selected kinds (eg "class")', + help_jsDocCompatibility: + "Sets compatibility options for comment parsing that increase similarity with JSDoc comments", + help_commentStyle: "Determines how TypeDoc searches for comments", + help_useTsLinkResolution: + "Use TypeScript's link resolution when determining where @link tags point. This only applies to JSDoc style comments", + help_preserveLinkText: + "If set, @link tags without link text will use the text content as the link. If not set, will use the target reflection name", + help_blockTags: + "Block tags which TypeDoc should recognize when parsing comments", + help_inlineTags: + "Inline tags which TypeDoc should recognize when parsing comments", + help_modifierTags: + "Modifier tags which TypeDoc should recognize when parsing comments", + help_categorizeByGroup: + "Specify whether categorization will be done at the group level", + help_defaultCategory: + "Specify the default category for reflections without a category", + help_categoryOrder: + "Specify the order in which categories appear. * indicates the relative order for categories not in the list", + help_groupOrder: + "Specify the order in which groups appear. * indicates the relative order for groups not in the list", + help_sort: "Specify the sort strategy for documented values", + help_sortEntryPoints: + "If set, entry points will be subject to the same sorting rules as other reflections", + help_kindSortOrder: + "Specify the sort order for reflections when 'kind' is specified", + help_watch: "Watch files for changes and rebuild docs on change", + help_preserveWatchOutput: + "If set, TypeDoc will not clear the screen between compilation runs", + help_skipErrorChecking: + "Do not run TypeScript's type checking before generating docs", + help_help: "Print this message", + help_version: "Print TypeDoc's version", + help_showConfig: "Print the resolved configuration and exit", + help_plugin: + "Specify the npm plugins that should be loaded. Omit to load all installed plugins", + help_logLevel: "Specify what level of logging should be used", + help_treatWarningsAsErrors: + "If set, all warnings will be treated as errors", + help_treatValidationWarningsAsErrors: + "If set, warnings emitted during validation will be treated as errors. This option cannot be used to disable treatWarningsAsErrors for validation warnings", + help_intentionallyNotExported: + "A list of types which should not produce 'referenced but not documented' warnings", + help_requiredToBeDocumented: + "A list of reflection kinds that must be documented", + help_validation: + "Specify which validation steps TypeDoc should perform on your generated documentation", + + // ================================================================== + // Option validation + // ================================================================== + unknown_option_0_you_may_have_meant_1: `Unknown option '{0}' You may have meant:\n\t{1}`, + option_0_must_be_between_1_and_2: "{0} must be between {1} and {2}", + option_0_must_be_equal_to_or_greater_than_1: + "{0} must be equal to or greater than {1}", + option_0_must_be_less_than_or_equal_to_1: + "{0} must be less than or equal to {1}", + option_0_must_be_one_of_1: "{0} must be one of {1}", + flag_0_is_not_valid_for_1_expected_2: + "The flag '{0}' is not valid for {1}, expected one of {2}", + expected_object_with_flag_values_for_0: + "Expected an object with flag values for {0} or true/false", + flag_values_for_0_must_be_booleans: "Flag values for {0} must be a boolean", + locales_must_be_an_object: + "The 'locales' option must be set to an object which resembles: { en: { theme_implements: \"Implements\" }}", + exclude_not_documented_specified_0_valid_values_are_1: `excludeNotDocumentedKinds may only specify known values, and invalid values were provided ({0}). The valid kinds are:\n{1}`, + external_symbol_link_mappings_must_be_object: + "externalSymbolLinkMappings must be a Record>", + highlight_theme_0_must_be_one_of_1: "{0} must be one of the following: {1}", + highlightLanguages_contains_invalid_languages_0: + "highlightLanguages contains invalid languages: {0}, run typedoc --help for a list of supported languages", + hostedBaseUrl_must_start_with_http: + "hostedBaseUrl must start with http:// or https://", + useHostedBaseUrlForAbsoluteLinks_requires_hostedBaseUrl: + "The useHostedBaseUrlForAbsoluteLinks option requires that hostedBaseUrl be set", + option_0_must_be_an_object: "The '{0}' option must be a non-array object", + 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}`, + visibility_filters_must_be_booleans: `All values of visibilityFilters must be booleans`, + 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}`, + + // ReflectionKind singular translations + kind_project: "Project", + kind_module: "Module", + kind_namespace: "Namespace", + kind_enum: "Enumeration", + kind_enum_member: "Enumeration Member", + kind_variable: "Variable", + kind_function: "Function", + kind_class: "Class", + kind_interface: "Interface", + kind_constructor: "Constructor", + kind_property: "Property", + kind_method: "Method", + kind_call_signature: "Call Signature", + kind_index_signature: "Index Signature", + kind_constructor_signature: "Constructor Signature", + kind_parameter: "Parameter", + kind_type_literal: "Type Literal", + kind_type_parameter: "Type Parameter", + kind_accessor: "Accessor", + kind_get_signature: "Get Signature", + kind_set_signature: "Set Signature", + kind_type_alias: "Type Alias", + kind_reference: "Reference", + kind_document: "Document", + + // ReflectionKind plural translations + kind_plural_project: "Projects", + kind_plural_module: "Modules", + kind_plural_namespace: "Namespaces", + kind_plural_enum: "Enumerations", + kind_plural_enum_member: "Enumeration Members", + kind_plural_variable: "Variables", + kind_plural_function: "Functions", + kind_plural_class: "Classes", + kind_plural_interface: "Interfaces", + kind_plural_constructor: "Constructors", + kind_plural_property: "Properties", + kind_plural_method: "Methods", + kind_plural_call_signature: "Call Signatures", + kind_plural_index_signature: "Index Signatures", + kind_plural_constructor_signature: "Constructor Signatures", + kind_plural_parameter: "Parameters", + kind_plural_type_literal: "Type Literals", + kind_plural_type_parameter: "Type Parameters", + kind_plural_accessor: "Accessors", + kind_plural_get_signature: "Get Signatures", + kind_plural_set_signature: "Set Signatures", + kind_plural_type_alias: "Type Aliases", + kind_plural_reference: "References", + kind_plural_document: "Documents", + + // ReflectionFlag translations + flag_private: "Private", + flag_protected: "Protected", + flag_public: "Public", + flag_static: "Static", + flag_external: "External", + flag_optional: "Optional", + flag_rest: "Rest", + flag_abstract: "Abstract", + flag_const: "Const", + flag_readonly: "Readonly", + flag_inherited: "Inherited", + + // ================================================================== + // Strings that show up in the default theme + // ================================================================== + // Page headings/labels + theme_implements: "Implements", + theme_indexable: "Indexable", + theme_type_declaration: "Type declaration", + theme_index: "Index", + theme_hierarchy: "Hierarchy", + theme_hierarchy_view_full: "view full", + theme_implemented_by: "Implemented by", + theme_defined_in: "Defined in", + theme_implementation_of: "Implementation of", + theme_inherited_from: "Inherited from", + theme_overrides: "Overrides", + theme_returns: "Returns", + theme_re_exports: "Re-exports", + theme_renames_and_re_exports: "Renames and re-exports", + theme_generated_using_typedoc: "Generated using TypeDoc", // If this includes "TypeDoc", theme will insert a link at that location. + theme_class_hierarchy_title: "Class Hierarchy", + // Search + theme_preparing_search_index: "Preparing search index...", + theme_search_index_not_available: "The search index is not available", + // Left nav bar + theme_loading: "Loading...", + // Right nav bar + theme_settings: "Settings", + theme_member_visibility: "Member Visibility", + theme_theme: "Theme", + theme_os: "OS", + theme_light: "Light", + theme_dark: "Dark", + theme_on_this_page: "On This Page", + + // aria-label + theme_search: "Search", + theme_menu: "Menu", + theme_permalink: "Permalink", + + // Used by the frontend JS + theme_copy: "Copy", + theme_copied: "Copied!", + theme_normally_hidden: + "This member is normally hidden due to your filter settings.", +} as const; + +export type BuiltinTranslatableStringArgs = { + [K in keyof typeof translatable]: BuildTranslationArguments< + (typeof translatable)[K] + >; +} & Record< + + | (typeof blockTags)[number] + | (typeof inlineTags)[number] + | (typeof modifierTags)[number] extends `@${infer T}` + ? `tag_${T}` + : never, + [] +>; + +type BuildTranslationArguments< + T extends string, + Acc extends any[] = [], +> = T extends `${string}{${bigint}}${infer R}` + ? BuildTranslationArguments + : Acc; + +export type BuiltinTranslatableStringConstraints = { + [K in keyof BuiltinTranslatableStringArgs]: TranslationConstraint[BuiltinTranslatableStringArgs[K]["length"]]; +}; + +type BuildConstraint< + T extends number, + Acc extends string = "", + U extends number = T, +> = [T] extends [never] + ? `${Acc}${string}` + : T extends T + ? BuildConstraint, `${Acc}${string}{${T}}`> + : never; + +// Combinatorially explosive, but shouldn't matter for us, since we only need a few iterations. +type TranslationConstraint = [ + string, + BuildConstraint<0>, + BuildConstraint<0 | 1>, + BuildConstraint<0 | 1 | 2>, +]; + +// Compiler errors here which says a property is missing indicates that the value on translatable +// is not a literal string. It should be so that TypeDoc's placeholder replacement detection +// can validate that all placeholders have been specified. +const _validateLiteralStrings: { + [K in keyof typeof translatable as string extends (typeof translatable)[K] + ? K + : never]: never; +} = {}; +_validateLiteralStrings; + +// Compiler errors here which says a property is missing indicates that the key on translatable +// contains a placeholder _0/_1, etc. but the value does not match the expected constraint. +const _validatePlaceholdersPresent: { + [K in keyof typeof translatable]: K extends `${string}_1${string}` + ? TranslationConstraint[2] + : K extends `${string}_0${string}` + ? TranslationConstraint[1] + : TranslationConstraint[0]; +} = translatable; +_validatePlaceholdersPresent; diff --git a/src/lib/models/FileRegistry.ts b/src/lib/models/FileRegistry.ts new file mode 100644 index 000000000..cedd8add3 --- /dev/null +++ b/src/lib/models/FileRegistry.ts @@ -0,0 +1,181 @@ +import { basename, dirname, parse, relative, resolve } from "path"; +import type { Deserializer, Serializer } from "../serialization"; +import type { FileRegistry as JSONFileRegistry } from "../serialization/schema"; +import { isFile, normalizePath } from "../utils"; +import type { Reflection } from "./reflections"; + +export class FileRegistry { + protected nextId = 1; + + // The combination of these two make up the registry + protected mediaToReflection = new Map(); + protected mediaToPath = new Map(); + + protected reflectionToPath = new Map(); + protected pathToMedia = new Map(); + + // Lazily created as we get names for rendering + protected names = new Map(); + protected nameUsage = new Map(); + + registerAbsolute(absolute: string) { + absolute = normalizePath(absolute); + const existing = this.pathToMedia.get(absolute); + if (existing) { + return existing; + } + + this.mediaToPath.set(this.nextId, absolute); + this.pathToMedia.set(absolute, this.nextId); + + return this.nextId++; + } + + /** Called by {@link ProjectReflection.registerReflection} @internal*/ + registerReflection(absolute: string, reflection: Reflection) { + absolute = normalizePath(absolute); + const id = this.registerAbsolute(absolute); + this.reflectionToPath.set(reflection, absolute); + this.mediaToReflection.set(id, reflection); + } + + register(sourcePath: string, relativePath: string): number | undefined { + return this.registerAbsolute( + resolve(dirname(sourcePath), relativePath), + ); + } + + removeReflection(reflection: Reflection): void { + const absolute = this.reflectionToPath.get(reflection); + if (absolute) { + const media = this.pathToMedia.get(absolute)!; + this.mediaToReflection.delete(media); + } + } + + resolve(id: number): string | Reflection | undefined { + return this.mediaToReflection.get(id) ?? this.mediaToPath.get(id); + } + + getName(id: number): string | undefined { + const absolute = this.mediaToPath.get(id); + if (!absolute) return; + + const file = basename(absolute); + if (!this.nameUsage.has(file)) { + this.nameUsage.set(file, 1); + this.names.set(id, file); + } else { + const { name, ext } = parse(file); + let counter = this.nameUsage.get(file)!; + while (this.nameUsage.has(`${name}-${counter}${ext}`)) { + ++counter; + } + this.nameUsage.set(file, counter + 1); + this.nameUsage.set(`${name}-${counter}${ext}`, counter + 1); + this.names.set(id, `${name}-${counter}${ext}`); + } + + return this.names.get(id); + } + + getNameToAbsoluteMap(): ReadonlyMap { + const result = new Map(); + for (const [id, name] of this.names.entries()) { + result.set(name, this.mediaToPath.get(id)!); + } + return result; + } + + toObject(ser: Serializer): JSONFileRegistry { + const result: JSONFileRegistry = { + entries: {}, + reflections: {}, + }; + + for (const [key, val] of this.mediaToPath.entries()) { + result.entries[key] = normalizePath(relative(ser.projectRoot, val)); + } + for (const [key, val] of this.mediaToReflection.entries()) { + result.reflections[key] = val.id; + } + + return result; + } + + /** + * Revive a file registry from disc. + * Note that in the packages context this may be called multiple times on + * a single object, and should merge in files from the other registries. + */ + fromObject(de: Deserializer, obj: JSONFileRegistry): void { + for (const [key, val] of Object.entries(obj.entries)) { + const absolute = normalizePath(resolve(de.projectRoot, val)); + de.oldFileIdToNewFileId[+key] = this.registerAbsolute(absolute); + } + + de.defer((project) => { + for (const [media, reflId] of Object.entries(obj.reflections)) { + const refl = project.getReflectionById( + de.oldIdToNewId[reflId]!, + ); + if (refl) { + this.mediaToReflection.set( + de.oldFileIdToNewFileId[+media]!, + refl, + ); + } + } + }); + } +} + +export class ValidatingFileRegistry extends FileRegistry { + override register( + sourcePath: string, + relativePath: string, + ): number | undefined { + const absolute = resolve(dirname(sourcePath), relativePath); + if (!isFile(absolute)) { + return; + } + return this.registerAbsolute(absolute); + } + + override fromObject(de: Deserializer, obj: JSONFileRegistry) { + for (const [key, val] of Object.entries(obj.entries)) { + const absolute = normalizePath(resolve(de.projectRoot, val)); + if (!isFile(absolute)) { + de.logger.warn( + de.logger.i18n.saved_relative_path_0_resolved_from_1_is_not_a_file( + val, + de.projectRoot, + ), + ); + continue; + } + + de.oldFileIdToNewFileId[+key] = this.registerAbsolute(absolute); + } + + de.defer((project) => { + for (const [media, reflId] of Object.entries(obj.reflections)) { + const refl = project.getReflectionById( + de.oldIdToNewId[reflId]!, + ); + if (refl) { + this.mediaToReflection.set( + de.oldFileIdToNewFileId[+media]!, + refl, + ); + } else { + de.logger.warn( + de.logger.i18n.serialized_project_referenced_0_not_part_of_project( + reflId.toString(), + ), + ); + } + } + }); + } +} diff --git a/src/lib/models/ReflectionCategory.ts b/src/lib/models/ReflectionCategory.ts index ae07a5fe5..2d2887008 100644 --- a/src/lib/models/ReflectionCategory.ts +++ b/src/lib/models/ReflectionCategory.ts @@ -1,5 +1,9 @@ import { Comment } from "./comments"; -import type { CommentDisplayPart, DeclarationReflection } from "."; +import type { + CommentDisplayPart, + DeclarationReflection, + DocumentReflection, +} from "."; import type { Serializer, JSONOutput, Deserializer } from "../serialization"; /** @@ -23,7 +27,7 @@ export class ReflectionCategory { /** * All reflections of this category. */ - children: DeclarationReflection[] = []; + children: Array = []; /** * Create a new ReflectionCategory instance. diff --git a/src/lib/models/ReflectionGroup.ts b/src/lib/models/ReflectionGroup.ts index 2f56d164d..983ec3d45 100644 --- a/src/lib/models/ReflectionGroup.ts +++ b/src/lib/models/ReflectionGroup.ts @@ -1,6 +1,11 @@ import { ReflectionCategory } from "./ReflectionCategory"; import { Comment } from "./comments"; -import type { CommentDisplayPart, DeclarationReflection, Reflection } from "."; +import type { + CommentDisplayPart, + DeclarationReflection, + DocumentReflection, + Reflection, +} from "."; import type { Serializer, JSONOutput, Deserializer } from "../serialization"; /** @@ -24,7 +29,7 @@ export class ReflectionGroup { /** * All reflections of this group. */ - children: DeclarationReflection[] = []; + children: Array = []; /** * Categories contained within this group. diff --git a/src/lib/models/comments/comment.ts b/src/lib/models/comments/comment.ts index 26096448d..e074ed8a2 100644 --- a/src/lib/models/comments/comment.ts +++ b/src/lib/models/comments/comment.ts @@ -1,18 +1,35 @@ import { assertNever, removeIf } from "../../utils"; import type { Reflection } from "../reflections"; -import { ReflectionKind } from "../reflections/kind"; import { ReflectionSymbolId } from "../reflections/ReflectionSymbolId"; import type { Serializer, Deserializer, JSONOutput } from "../../serialization"; +import { NonEnumerable } from "../../utils/general"; /** * Represents a parsed piece of a comment. * @category Comments + * @see {@link JSONOutput.CommentDisplayPart} */ export type CommentDisplayPart = + /** + * Represents a plain text portion of the comment, may contain markdown + */ | { kind: "text"; text: string } + /** + * Represents a code block separated out form the plain text entry so + * that TypeDoc knows to skip it when parsing relative links and inline tags. + **/ | { kind: "code"; text: string } - | InlineTagDisplayPart; + /** + * Represents an inline tag like `{@link Foo}` + */ + | InlineTagDisplayPart + /** + * Represents a reference to a path relative to where the comment resides. + * This is used to detect and copy relative image links. + * Use {@link FileRegistry} to determine what path on disc this refers to. + */ + | RelativeLinkDisplayPart; /** * The `@link`, `@linkcode`, and `@linkplain` tags may have a `target` @@ -29,6 +46,24 @@ export interface InlineTagDisplayPart { tsLinkText?: string; } +/** + * This is used for relative links within comments/documents. + * It is used to mark pieces of text which need to be replaced + * to make links work properly. + */ +export interface RelativeLinkDisplayPart { + kind: "relative-link"; + /** + * The original relative text from the parsed comment. + */ + text: string; + /** + * 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; +} + /** * A model that represents a single TypeDoc comment tag. * @@ -52,6 +87,13 @@ export class CommentTag { */ content: CommentDisplayPart[]; + /** + * A flag which may be set by plugins to prevent TypeDoc from rendering this tag, if the plugin provides + * custom rendering. Note: This flag is **not** serialized, it is expected to be set just before the comment + * is rendered. + */ + skipRendering = false; + /** * Create a new CommentTag instance. */ @@ -107,6 +149,7 @@ export class Comment { switch (item.kind) { case "text": case "code": + case "relative-link": result += item.text; break; case "inline-tag": @@ -120,77 +163,6 @@ export class Comment { return result; } - /** - * Helper function to convert an array of comment display parts into markdown suitable for - * passing into Marked. `urlTo` will be used to resolve urls to any reflections linked to with - * `@link` tags. - */ - static displayPartsToMarkdown( - parts: readonly CommentDisplayPart[], - urlTo: (ref: Reflection) => string, - ) { - const result: string[] = []; - - for (const part of parts) { - switch (part.kind) { - case "text": - case "code": - result.push(part.text); - break; - case "inline-tag": - switch (part.tag) { - case "@label": - case "@inheritdoc": // Shouldn't happen - break; // Not rendered. - case "@link": - case "@linkcode": - case "@linkplain": { - if (part.target) { - let url: string | undefined; - let kindClass: string | undefined; - if (typeof part.target === "string") { - url = part.target; - } else if (part.target && "id" in part.target) { - // No point in trying to resolve a ReflectionSymbolId at this point, we've already - // tried and failed during the resolution step. - url = urlTo(part.target); - kindClass = ReflectionKind.classString( - part.target.kind, - ); - } - const text = - part.tag === "@linkcode" - ? `${part.text}` - : part.text; - result.push( - url - ? `${text}` - : part.text, - ); - } else { - result.push(part.text); - } - break; - } - default: - // Hmm... probably want to be able to render these somehow, so custom inline tags can be given - // special rendering rules. Future capability. For now, just render their text. - result.push(`{${part.tag} ${part.text}}`); - break; - } - break; - default: - assertNever(part); - } - } - - return result.join(""); - } - /** * Helper utility to clone {@link Comment.summary} or {@link CommentTag.content} */ @@ -233,6 +205,11 @@ export class Comment { target, }; } + case "relative-link": { + return { + ...part, + }; + } } }); } @@ -242,7 +219,11 @@ export class Comment { de: Deserializer, parts: JSONOutput.CommentDisplayPart[], ): CommentDisplayPart[] { - const links: [number, InlineTagDisplayPart][] = []; + const links: [ + number, + InlineTagDisplayPart | RelativeLinkDisplayPart, + ][] = []; + const files: [number, RelativeLinkDisplayPart][] = []; const result = parts.map((part): CommentDisplayPart => { switch (part.kind) { @@ -282,19 +263,37 @@ export class Comment { } else { assertNever(part.target); } + break; + } + case "relative-link": { + if (part.target) { + const part2 = { + kind: "relative-link", + text: part.text, + target: null!, + } satisfies RelativeLinkDisplayPart; + files.push([part.target, part2]); + return part2; + } + return { ...part, target: undefined }; } } }); - if (links.length) { + if (links.length || files.length) { de.defer((project) => { + for (const [oldFileId, part] of files) { + part.target = de.oldFileIdToNewFileId[oldFileId]; + } for (const [oldId, part] of links) { part.target = project.getReflectionById( de.oldIdToNewId[oldId] ?? -1, ); if (!part.target) { de.logger.warn( - `Serialized project contained a link to ${oldId} (${part.text}), which was not a part of the project.`, + de.application.i18n.serialized_project_referenced_0_not_part_of_project( + oldId.toString(), + ), ); } } @@ -319,6 +318,7 @@ export class Comment { case "code": return part.text.includes("\n"); case "inline-tag": + case "relative-link": return false; } }); @@ -379,6 +379,33 @@ export class Comment { */ label?: string; + /** + * Full path to the file where this comment originated from, if any. + * This field will not be serialized, so will not be present when handling JSON-revived reflections. + * + * Note: This field is non-enumerable to make testing comment contents with `deepEqual` easier. + */ + @NonEnumerable + sourcePath?: string; + + /** + * Internal discovery ID used to prevent symbol comments from + * being duplicated on signatures. Only set when the comment was created + * from a `ts.CommentRange`. + * @internal + */ + @NonEnumerable + discoveryId?: number; + + /** + * If the comment was inherited from a different "parent" declaration + * (see #2545), then it is desirable to know this as any `@param` tags + * which do not apply should not cause warnings. This is not serialized, + * and only set when the comment was created from a `ts.CommentRange`. + */ + @NonEnumerable + inheritedFromParentDeclaration?: boolean; + /** * Creates a new Comment instance. */ @@ -397,11 +424,16 @@ export class Comment { * Create a deep clone of this comment. */ clone() { - return new Comment( + const comment = new Comment( Comment.cloneDisplayParts(this.summary), this.blockTags.map((tag) => tag.clone()), new Set(this.modifierTags), ); + comment.discoveryId = this.discoveryId; + comment.sourcePath = this.sourcePath; + comment.inheritedFromParentDeclaration = + this.inheritedFromParentDeclaration; + return comment; } /** diff --git a/src/lib/models/comments/index.ts b/src/lib/models/comments/index.ts index 15dc5eeb4..9e2becc76 100644 --- a/src/lib/models/comments/index.ts +++ b/src/lib/models/comments/index.ts @@ -1,2 +1,6 @@ export { Comment, CommentTag } from "./comment"; -export type { CommentDisplayPart, InlineTagDisplayPart } from "./comment"; +export type { + CommentDisplayPart, + InlineTagDisplayPart, + RelativeLinkDisplayPart, +} from "./comment"; diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 7d31fb07d..76b2a69fa 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -4,3 +4,4 @@ export * from "./comments/index"; export * from "./sources/index"; export * from "./ReflectionGroup"; export * from "./ReflectionCategory"; +export * from "./FileRegistry"; diff --git a/src/lib/models/reflections/ReflectionSymbolId.ts b/src/lib/models/reflections/ReflectionSymbolId.ts index ee2728179..904d79016 100644 --- a/src/lib/models/reflections/ReflectionSymbolId.ts +++ b/src/lib/models/reflections/ReflectionSymbolId.ts @@ -46,7 +46,7 @@ export class ReflectionSymbolId { declaration?: ts.Declaration, ) { if ("name" in symbol) { - declaration ??= symbol?.declarations?.[0]; + declaration ??= symbol.declarations?.[0]; this.fileName = normalizePath( declaration?.getSourceFile().fileName ?? "", ); diff --git a/src/lib/models/reflections/abstract.ts b/src/lib/models/reflections/abstract.ts index 5f027b0ea..f0d713816 100644 --- a/src/lib/models/reflections/abstract.ts +++ b/src/lib/models/reflections/abstract.ts @@ -6,7 +6,12 @@ import { ReflectionKind } from "./kind"; import type { Serializer, Deserializer, JSONOutput } from "../../serialization"; import type { ReflectionVariant } from "./variant"; import type { DeclarationReflection } from "./declaration"; +import type { DocumentReflection } from "./document"; import { NonEnumerable } from "../../utils/general"; +import type { + Internationalization, + TranslatedString, +} from "../../internationalization"; /** * Current reflection id. @@ -24,28 +29,24 @@ export function resetReflectionID() { export enum ReflectionFlag { None = 0, - Private = 1, - Protected = 2, - Public = 4, - Static = 8, - ExportAssignment = 16, - External = 32, - Optional = 64, - DefaultValue = 128, - Rest = 256, - Abstract = 512, - Const = 1024, - Let = 2048, - Readonly = 4096, + Private = 1 << 0, + Protected = 1 << 1, + Public = 1 << 2, + Static = 1 << 3, + External = 1 << 4, + Optional = 1 << 5, + Rest = 1 << 6, + Abstract = 1 << 7, + Const = 1 << 8, + Readonly = 1 << 9, + Inherited = 1 << 10, } const relevantFlags: ReflectionFlag[] = [ ReflectionFlag.Private, ReflectionFlag.Protected, ReflectionFlag.Static, - ReflectionFlag.ExportAssignment, ReflectionFlag.Optional, - ReflectionFlag.DefaultValue, ReflectionFlag.Rest, ReflectionFlag.Abstract, ReflectionFlag.Const, @@ -55,7 +56,7 @@ const relevantFlags: ReflectionFlag[] = [ /** * This must extend Array in order to work with Handlebar's each helper. */ -export class ReflectionFlags extends Array { +export class ReflectionFlags { private flags = ReflectionFlag.None; hasFlag(flag: ReflectionFlag) { @@ -113,10 +114,6 @@ export class ReflectionFlags extends Array { return this.hasFlag(ReflectionFlag.Rest); } - get hasExportAssignment(): boolean { - return this.hasFlag(ReflectionFlag.ExportAssignment); - } - get isAbstract(): boolean { return this.hasFlag(ReflectionFlag.Abstract); } @@ -129,6 +126,10 @@ export class ReflectionFlags extends Array { return this.hasFlag(ReflectionFlag.Readonly); } + get isInherited() { + return this.hasFlag(ReflectionFlag.Inherited); + } + setFlag(flag: ReflectionFlag, set: boolean) { switch (flag) { case ReflectionFlag.Private: @@ -157,20 +158,20 @@ export class ReflectionFlags extends Array { } } + getFlagStrings(i18n: Internationalization) { + const strings: TranslatedString[] = []; + for (const flag of relevantFlags) { + if (this.hasFlag(flag)) { + strings.push(i18n.flagString(flag)); + } + } + return strings; + } + private setSingleFlag(flag: ReflectionFlag, set: boolean) { - const name = ReflectionFlag[flag].replace( - /(.)([A-Z])/g, - (_m, a, b) => a + " " + b.toLowerCase(), - ); if (!set && this.hasFlag(flag)) { - if (relevantFlags.includes(flag)) { - this.splice(this.indexOf(name), 1); - } this.flags ^= flag; } else if (set && !this.hasFlag(flag)) { - if (relevantFlags.includes(flag)) { - this.push(name); - } this.flags |= flag; } } @@ -183,10 +184,10 @@ export class ReflectionFlags extends Array { "isExternal", "isOptional", "isRest", - "hasExportAssignment", "isAbstract", "isConst", "isReadonly", + "isInherited", ] as const; toObject(): JSONOutput.ReflectionFlags { @@ -212,6 +213,7 @@ export class ReflectionFlags extends Array { export enum TraverseProperty { Children, + Documents, Parameters, TypeLiteral, TypeParameter, @@ -329,8 +331,10 @@ export abstract class Reflection { * Test whether this reflection is of the given kind. */ kindOf(kind: ReflectionKind | ReflectionKind[]): boolean { - const kindArray = Array.isArray(kind) ? kind : [kind]; - return kindArray.some((kind) => (this.kind & kind) !== 0); + const kindFlags = Array.isArray(kind) + ? kind.reduce((a, b) => a | b, 0) + : kind; + return (this.kind & kindFlags) !== 0; } /** @@ -384,36 +388,39 @@ export abstract class Reflection { * Return an url safe alias for this reflection. */ getAlias(): string { - if (!this._alias) { - let alias = this.name.replace(/\W/g, "_"); - if (alias === "") { - alias = "reflection-" + this.id; - } - // NTFS/ExFAT use uppercase, so we will too. It probably won't matter - // in this case since names will generally be valid identifiers, but to be safe... - const upperAlias = alias.toUpperCase(); + this._alias ||= this.getUniqueAliasInPage( + this.name.replace(/\W/g, "_") || `reflection-${this.id}`, + ); - let target = this as Reflection; - while (target.parent && !target.hasOwnDocument) { - target = target.parent; - } + return this._alias; + } - target._aliases ||= new Map(); + // This really ought not live here, it ought to live in the html renderer, but moving that + // is more work than I want right now, it can wait for 0.27 when trying to split models into + // a bundleable structure. + getUniqueAliasInPage(heading: string) { + // NTFS/ExFAT use uppercase, so we will too. It probably won't matter + // in this case since names will generally be valid identifiers, but to be safe... + const upperAlias = heading.toUpperCase(); - let suffix = ""; - if (!target._aliases.has(upperAlias)) { - target._aliases.set(upperAlias, 1); - } else { - const count = target._aliases.get(upperAlias)!; - suffix = "-" + count.toString(); - target._aliases.set(upperAlias, count + 1); - } + let target = this as Reflection; + while (target.parent && !target.hasOwnDocument) { + target = target.parent; + } - alias += suffix; - this._alias = alias; + target._aliases ||= new Map(); + + let suffix = ""; + if (!target._aliases.has(upperAlias)) { + target._aliases.set(upperAlias, 1); + } else { + const count = target._aliases.get(upperAlias)!; + suffix = "-" + count.toString(); + target._aliases.set(upperAlias, count + 1); } - return this._alias; + heading += suffix; + return heading; } /** @@ -466,18 +473,21 @@ export abstract class Reflection { isDeclaration(): this is DeclarationReflection { return false; } + isDocument(): this is DocumentReflection { + return false; + } /** * Check if this reflection or any of its parents have been marked with the `@deprecated` tag. */ isDeprecated(): boolean { - let signaturesDeprecated = false; + let signaturesDeprecated = false as boolean; this.visit({ declaration(decl) { if ( decl.signatures?.length && - decl.signatures.every( - (sig) => sig.comment?.getTag("@deprecated"), + decl.signatures.every((sig) => + sig.comment?.getTag("@deprecated"), ) ) { signaturesDeprecated = true; @@ -496,7 +506,7 @@ export abstract class Reflection { * Traverse most potential child reflections of this reflection. * * Note: This may not necessarily traverse child reflections contained within the `type` property - * of the reflection, and should not be relied on for this. Support for checking object types will likely be removed in v0.26. + * of the reflection, and should not be relied on for this. Support for checking object types will likely be removed in v0.27. * * The given callback will be invoked for all children, signatures and type parameters * attached to this reflection. diff --git a/src/lib/models/reflections/container.ts b/src/lib/models/reflections/container.ts index b58a428db..72a8308c4 100644 --- a/src/lib/models/reflections/container.ts +++ b/src/lib/models/reflections/container.ts @@ -1,18 +1,47 @@ -import { Reflection, TraverseCallback, TraverseProperty } from "./abstract"; +import { + Reflection, + type TraverseCallback, + TraverseProperty, +} from "./abstract"; import { ReflectionCategory } from "../ReflectionCategory"; import { ReflectionGroup } from "../ReflectionGroup"; import type { ReflectionKind } from "./kind"; import type { Serializer, JSONOutput, Deserializer } from "../../serialization"; +import type { DocumentReflection } from "./document"; import type { DeclarationReflection } from "./declaration"; +import { removeIfPresent } from "../../utils"; /** * @category Reflections */ export abstract class ContainerReflection extends Reflection { /** - * The children of this reflection. + * The children of this reflection. Do not add reflections to this array + * manually. Instead call {@link addChild}. */ - children?: DeclarationReflection[]; + children?: Array; + + /** + * Documents associated with this reflection. + * + * These are not children as including them as children requires code handle both + * types, despite being mostly unrelated and handled separately. + * + * Including them here in a separate array neatly handles that problem, but also + * introduces another one for rendering. When rendering, documents should really + * actually be considered part of the "children" of a reflection. For this reason, + * we also maintain a list of child declarations with child documents which is used + * when rendering. + */ + documents?: Array; + + /** + * Union of the {@link children} and {@link documents} arrays which dictates the + * sort order for rendering. + */ + childrenIncludingDocuments?: Array< + DeclarationReflection | DocumentReflection + >; /** * All children grouped by their kind. @@ -34,18 +63,63 @@ export abstract class ContainerReflection extends Reflection { return (this.children || []).filter((child) => child.kindOf(kind)); } + addChild(child: DeclarationReflection | DocumentReflection) { + if (child.isDeclaration()) { + this.children ||= []; + this.children.push(child); + } else { + this.documents ||= []; + this.documents.push(child); + } + + this.childrenIncludingDocuments ||= []; + this.childrenIncludingDocuments.push(child); + } + + removeChild(child: DeclarationReflection | DocumentReflection) { + if (child.isDeclaration()) { + removeIfPresent(this.children, child); + if (this.children?.length === 0) { + delete this.children; + } + } else { + removeIfPresent(this.documents, child); + if (this.documents?.length === 0) { + delete this.documents; + } + } + + removeIfPresent(this.childrenIncludingDocuments, child); + if (this.childrenIncludingDocuments?.length === 0) { + delete this.childrenIncludingDocuments; + } + } + override traverse(callback: TraverseCallback) { for (const child of this.children?.slice() || []) { if (callback(child, TraverseProperty.Children) === false) { return; } } + + for (const child of this.documents?.slice() || []) { + if (callback(child, TraverseProperty.Documents) === false) { + return; + } + } } override toObject(serializer: Serializer): JSONOutput.ContainerReflection { return { ...super.toObject(serializer), children: serializer.toObjectsOptional(this.children), + documents: serializer.toObjectsOptional(this.documents), + // If we only have one type of child, don't bother writing the duplicate info about + // ordering with documents to the serialized file. + childrenIncludingDocuments: + this.children?.length && this.documents?.length + ? this.childrenIncludingDocuments?.map((refl) => refl.id) + : undefined, groups: serializer.toObjectsOptional(this.groups), categories: serializer.toObjectsOptional(this.categories), }; @@ -56,6 +130,35 @@ export abstract class ContainerReflection extends Reflection { this.children = de.reviveMany(obj.children, (child) => de.constructReflection(child), ); + this.documents = de.reviveMany(obj.documents, (child) => + de.constructReflection(child), + ); + + const byId = new Map< + number, + DeclarationReflection | DocumentReflection + >(); + for (const child of this.children || []) { + byId.set(child.id, child); + } + for (const child of this.documents || []) { + byId.set(child.id, child); + } + for (const id of obj.childrenIncludingDocuments || []) { + const child = byId.get(de.oldIdToNewId[id] ?? -1); + if (child) { + this.childrenIncludingDocuments ||= []; + this.childrenIncludingDocuments.push(child); + byId.delete(de.oldIdToNewId[id] ?? -1); + } + } + if (byId.size) { + // Anything left in byId wasn't included in the childrenIncludingDocuments array. + // This is expected if we're dealing with a JSON file produced by TypeDoc 0.25. + this.childrenIncludingDocuments ||= []; + this.childrenIncludingDocuments.push(...byId.values()); + } + this.groups = de.reviveMany( obj.groups, (group) => new ReflectionGroup(group.title, this), diff --git a/src/lib/models/reflections/declaration.ts b/src/lib/models/reflections/declaration.ts index a3fd3105e..084db47e8 100644 --- a/src/lib/models/reflections/declaration.ts +++ b/src/lib/models/reflections/declaration.ts @@ -1,11 +1,16 @@ import type * as ts from "typescript"; -import { ReferenceType, ReflectionType, Type, type SomeType } from "../types"; +import { + type ReferenceType, + ReflectionType, + type Type, + type SomeType, +} from "../types"; import { type TraverseCallback, TraverseProperty } from "./abstract"; import { ContainerReflection } from "./container"; import type { SignatureReflection } from "./signature"; import type { TypeParameterReflection } from "./type-parameter"; import type { Serializer, JSONOutput, Deserializer } from "../../serialization"; -import { Comment, CommentDisplayPart } from "../comments"; +import { Comment, type CommentDisplayPart } from "../comments"; import { SourceReference } from "../sources/file"; import { ReflectionSymbolId } from "./ReflectionSymbolId"; import { ReflectionKind } from "./kind"; @@ -32,14 +37,6 @@ export interface DeclarationHierarchy { isTarget?: boolean; } -/** - * @internal - */ -export enum ConversionFlags { - None = 0, - VariableOrPropertySource = 1, -} - /** * A reflection that represents a single declaration emitted by the TypeScript compiler. * @@ -91,7 +88,7 @@ export class DeclarationReflection extends ContainerReflection { /** * The index signature of this declaration. */ - indexSignature?: SignatureReflection; + indexSignatures?: SignatureReflection[]; /** * The get signature of this declaration. @@ -167,12 +164,6 @@ export class DeclarationReflection extends ContainerReflection { */ packageVersion?: string; - /** - * Flags for information about a reflection which is needed solely during conversion. - * @internal - */ - conversionFlags = ConversionFlags.None; - override isDeclaration(): this is DeclarationReflection { return true; } @@ -187,8 +178,8 @@ export class DeclarationReflection extends ContainerReflection { if (this.signatures) { result = result.concat(this.signatures); } - if (this.indexSignature) { - result.push(this.indexSignature); + if (this.indexSignatures) { + result = result.concat(this.indexSignatures); } if (this.getSignature) { result.push(this.getSignature); @@ -233,12 +224,9 @@ export class DeclarationReflection extends ContainerReflection { } } - if (this.indexSignature) { + for (const signature of this.indexSignatures?.slice() || []) { if ( - callback( - this.indexSignature, - TraverseProperty.IndexSignature, - ) === false + callback(signature, TraverseProperty.IndexSignature) === false ) { return; } @@ -298,7 +286,7 @@ export class DeclarationReflection extends ContainerReflection { typeParameters: serializer.toObjectsOptional(this.typeParameters), type: serializer.toObject(this.type), signatures: serializer.toObjectsOptional(this.signatures), - indexSignature: serializer.toObject(this.indexSignature), + indexSignatures: serializer.toObjectsOptional(this.indexSignatures), getSignature: serializer.toObject(this.getSignature), setSignature: serializer.toObject(this.setSignature), defaultValue: this.defaultValue, @@ -330,6 +318,7 @@ export class DeclarationReflection extends ContainerReflection { if (obj.variant === "project") { this.kind = ReflectionKind.Module; this.packageVersion = obj.packageVersion; + this.project.files.fromObject(de, obj.files || {}); de.defer(() => { for (const [id, sid] of Object.entries(obj.symbolIdMap || {})) { @@ -343,7 +332,9 @@ export class DeclarationReflection extends ContainerReflection { ); } else { de.logger.warn( - `Serialized project contained a reflection with id ${id} but it was not present in deserialized project.`, + de.application.i18n.serialized_project_referenced_0_not_part_of_project( + id.toString(), + ), ); } } @@ -365,9 +356,17 @@ export class DeclarationReflection extends ContainerReflection { this.signatures = de.reviveMany(obj.signatures, (r) => de.constructReflection(r), ); - this.indexSignature = de.revive(obj.indexSignature, (r) => - de.constructReflection(r), - ); + + // TypeDoc 0.25, remove check with 0.28. + if (obj.indexSignature) { + this.indexSignatures = [ + de.revive(obj.indexSignature, (r) => de.constructReflection(r)), + ]; + } else { + this.indexSignatures = de.reviveMany(obj.indexSignatures, (r) => + de.constructReflection(r), + ); + } this.getSignature = de.revive(obj.getSignature, (r) => de.constructReflection(r), ); diff --git a/src/lib/models/reflections/document.ts b/src/lib/models/reflections/document.ts new file mode 100644 index 000000000..74c6b54ec --- /dev/null +++ b/src/lib/models/reflections/document.ts @@ -0,0 +1,90 @@ +import type { Deserializer, JSONOutput, Serializer } from "../../serialization"; +import { Comment, type CommentDisplayPart } from "../comments"; +import { + Reflection, + TraverseProperty, + type TraverseCallback, +} from "./abstract"; +import { ReflectionKind } from "./kind"; + +/** + * Non-TS reflection type which is used to represent markdown documents included in the docs. + */ +export class DocumentReflection extends Reflection { + override readonly variant = "document"; + + /** + * The content to be displayed on the page for this reflection. + */ + content: CommentDisplayPart[]; + + /** + * Frontmatter included in document + */ + frontmatter: Record; + + /** + * A precomputed boost derived from the searchCategoryBoosts and searchGroupBoosts options, used when + * boosting search relevance scores at runtime. May be modified by plugins. + */ + relevanceBoost?: number; + + /** + * Child documents, if any are present. + */ + children?: DocumentReflection[]; + + constructor( + name: string, + parent: Reflection, + content: CommentDisplayPart[], + frontmatter: Record, + ) { + super(name, ReflectionKind.Document, parent); + this.content = content; + this.frontmatter = frontmatter; + + if (typeof frontmatter["title"] === "string") { + this.name = frontmatter["title"]; + delete frontmatter["title"]; + } + } + + addChild(child: DocumentReflection) { + this.children ||= []; + this.children.push(child); + } + + override isDocument(): this is DocumentReflection { + return true; + } + + override traverse(callback: TraverseCallback): void { + for (const child of this.children || []) { + if (callback(child, TraverseProperty.Documents) === false) { + return; + } + } + } + + override toObject(serializer: Serializer): JSONOutput.DocumentReflection { + return { + ...super.toObject(serializer), + variant: this.variant, + content: Comment.serializeDisplayParts(serializer, this.content), + frontmatter: this.frontmatter, + relevanceBoost: this.relevanceBoost, + children: serializer.toObjectsOptional(this.children), + }; + } + + override fromObject(de: Deserializer, obj: JSONOutput.DocumentReflection) { + super.fromObject(de, obj); + 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), + ); + } +} diff --git a/src/lib/models/reflections/index.ts b/src/lib/models/reflections/index.ts index 28cce65fe..31d8ac8cc 100644 --- a/src/lib/models/reflections/index.ts +++ b/src/lib/models/reflections/index.ts @@ -4,19 +4,20 @@ export { ReflectionFlags, TraverseProperty, } from "./abstract"; -export type { TraverseCallback, ReflectionVisitor } from "./abstract"; +export type { ReflectionVisitor, TraverseCallback } from "./abstract"; export { ContainerReflection } from "./container"; -export { DeclarationReflection, ConversionFlags } from "./declaration"; +export { DeclarationReflection } from "./declaration"; export type { DeclarationHierarchy } from "./declaration"; +export { DocumentReflection } from "./document"; export { ReflectionKind } from "./kind"; export { ParameterReflection } from "./parameter"; export { ProjectReflection } from "./project"; export { ReferenceReflection } from "./reference"; -export { SignatureReflection } from "./signature"; -export { TypeParameterReflection, VarianceModifier } from "./type-parameter"; -export { splitUnquotedString } from "./utils"; -export type { ReflectionVariant } from "./variant"; export { ReflectionSymbolId, type ReflectionSymbolIdString, } from "./ReflectionSymbolId"; +export { SignatureReflection } from "./signature"; +export { TypeParameterReflection, VarianceModifier } from "./type-parameter"; +export { splitUnquotedString } from "./utils"; +export type { ReflectionVariant } from "./variant"; diff --git a/src/lib/models/reflections/kind.ts b/src/lib/models/reflections/kind.ts index cc3c13eef..4e0f1d97e 100644 --- a/src/lib/models/reflections/kind.ts +++ b/src/lib/models/reflections/kind.ts @@ -28,12 +28,17 @@ export enum ReflectionKind { SetSignature = 0x100000, TypeAlias = 0x200000, Reference = 0x400000, + /** + * Generic non-ts content to be included in the generated docs as its own page. + */ + Document = 0x800000, } /** @category Reflections */ export namespace ReflectionKind { export type KindString = EnumKeys; + /** @internal */ export const All = ReflectionKind.Reference * 2 - 1; /** @internal */ @@ -86,6 +91,9 @@ export namespace ReflectionKind { ReflectionKind.TypeAlias | ReflectionKind.Reference; /** @internal */ + export const MayContainDocuments = + SomeExport | ReflectionKind.Project | ReflectionKind.Document; + /** @internal */ export const ExportContainer = ReflectionKind.SomeModule | ReflectionKind.Project; @@ -125,8 +133,10 @@ export namespace ReflectionKind { export const SignatureContainer = ContainsCallSignatures | ReflectionKind.Accessor; + /** @internal */ export const VariableContainer = SomeModule | ReflectionKind.Project; + /** @internal */ export const MethodContainer = ClassOrInterface | VariableOrProperty | @@ -146,6 +156,9 @@ export namespace ReflectionKind { [ReflectionKind.TypeAlias]: "Type Aliases", }; + /** + * Get a non-localized kind string. For the localized string, use `app.internationalization.kindSingularString(kind)` + */ export function singularString(kind: ReflectionKind): string { if (kind in SINGULARS) { return SINGULARS[kind as keyof typeof SINGULARS]; @@ -154,6 +167,9 @@ export namespace ReflectionKind { } } + /** + * Get a non-localized kind string. For the localized string, use `app.internationalization.kindPluralString(kind)` + */ export function pluralString(kind: ReflectionKind): string { if (kind in PLURALS) { return PLURALS[kind as keyof typeof PLURALS]; @@ -172,6 +188,6 @@ export namespace ReflectionKind { function getKindString(kind: ReflectionKind): string { return ReflectionKind[kind].replace( /(.)([A-Z])/g, - (_m, a, b) => a + " " + b.toLowerCase(), + (_m, a: string, b: string) => a + " " + b.toLowerCase(), ); } diff --git a/src/lib/models/reflections/parameter.ts b/src/lib/models/reflections/parameter.ts index 42087e19b..d0d482039 100644 --- a/src/lib/models/reflections/parameter.ts +++ b/src/lib/models/reflections/parameter.ts @@ -1,6 +1,10 @@ import type { SomeType } from ".."; import { ReflectionType } from "../types"; -import { Reflection, TraverseCallback, TraverseProperty } from "./abstract"; +import { + Reflection, + type TraverseCallback, + TraverseProperty, +} from "./abstract"; import type { SignatureReflection } from "./signature"; import type { Serializer, JSONOutput, Deserializer } from "../../serialization"; diff --git a/src/lib/models/reflections/project.ts b/src/lib/models/reflections/project.ts index 584df0a9e..d2a28f6b2 100644 --- a/src/lib/models/reflections/project.ts +++ b/src/lib/models/reflections/project.ts @@ -1,4 +1,4 @@ -import { Reflection, TraverseProperty } from "./abstract"; +import { type Reflection, TraverseProperty } from "./abstract"; import { ContainerReflection } from "./container"; import { ReferenceReflection } from "./reference"; import type { DeclarationReflection } from "./declaration"; @@ -6,14 +6,16 @@ import type { SignatureReflection } from "./signature"; import type { ParameterReflection } from "./parameter"; import { IntrinsicType } from "../types"; import type { TypeParameterReflection } from "./type-parameter"; -import { removeIf, removeIfPresent } from "../../utils"; +import { assertNever, removeIf, removeIfPresent } from "../../utils"; import type * as ts from "typescript"; import { ReflectionKind } from "./kind"; -import { Comment, CommentDisplayPart } from "../comments"; +import { Comment, type CommentDisplayPart } from "../comments"; import { ReflectionSymbolId } from "./ReflectionSymbolId"; import type { Serializer } from "../../serialization/serializer"; import type { Deserializer, JSONOutput } from "../../serialization/index"; import { DefaultMap, StableKeyMap } from "../../utils/map"; +import type { DocumentReflection } from "./document"; +import type { FileRegistry } from "../FileRegistry"; /** * A reflection that represents the root of the project. @@ -64,9 +66,15 @@ export class ProjectReflection extends ContainerReflection { */ readme?: CommentDisplayPart[]; - constructor(name: string) { + /** + * Object which describes where to find content for relative links. + */ + readonly files: FileRegistry; + + constructor(name: string, registry: FileRegistry) { super(name, ReflectionKind.Project); this.reflections[this.id] = this; + this.files = registry; } /** @@ -92,7 +100,11 @@ export class ProjectReflection extends ContainerReflection { * Registers the given reflection so that it can be quickly looked up by helper methods. * Should be called for *every* reflection added to the project. */ - registerReflection(reflection: Reflection, symbol?: ts.Symbol) { + registerReflection( + reflection: Reflection, + symbol: ts.Symbol | undefined, + filePath: string | undefined, + ) { this.referenceGraph = undefined; if (reflection.parent) { this.reflectionChildren @@ -135,6 +147,10 @@ export class ProjectReflection extends ContainerReflection { } } } + + if (filePath) { + this.files.registerReflection(filePath, reflection); + } } /** @@ -151,56 +167,72 @@ export class ProjectReflection extends ContainerReflection { // but I think that could only be caused by a plugin doing something weird, not by a regular // user... so this is probably good enough for now. Reflections that live on types are // kind of half-real anyways. - const parent = reflection.parent as DeclarationReflection; + const parent = reflection.parent as DeclarationReflection | undefined; parent?.traverse((child, property) => { if (child !== reflection) { return true; // Continue iteration } - if (property === TraverseProperty.Children) { - removeIfPresent( - parent.children, - reflection as DeclarationReflection, - ); - if (!parent.children?.length) { - delete parent.children; - } - } else if (property === TraverseProperty.GetSignature) { - delete parent.getSignature; - } else if (property === TraverseProperty.IndexSignature) { - delete parent.indexSignature; - } else if (property === TraverseProperty.Parameters) { - removeIfPresent( - (reflection.parent as SignatureReflection).parameters, - reflection as ParameterReflection, - ); - if ( - !(reflection.parent as SignatureReflection).parameters - ?.length - ) { - delete (reflection.parent as SignatureReflection) - .parameters; - } - } else if (property === TraverseProperty.SetSignature) { - delete parent.setSignature; - } else if (property === TraverseProperty.Signatures) { - removeIfPresent( - parent.signatures, - reflection as SignatureReflection, - ); - if (!parent.signatures?.length) { - delete parent.signatures; - } - } else if (property === TraverseProperty.TypeLiteral) { - parent.type = new IntrinsicType("Object"); - } else if (property === TraverseProperty.TypeParameter) { - removeIfPresent( - parent.typeParameters, - reflection as TypeParameterReflection, - ); - if (!parent.typeParameters?.length) { - delete parent.typeParameters; - } + switch (property) { + case TraverseProperty.Children: + case TraverseProperty.Documents: + parent.removeChild( + reflection as + | DeclarationReflection + | DocumentReflection, + ); + break; + case TraverseProperty.GetSignature: + delete parent.getSignature; + break; + case TraverseProperty.IndexSignature: + removeIfPresent( + parent.indexSignatures, + reflection as SignatureReflection, + ); + if (!parent.indexSignatures?.length) { + delete parent.indexSignatures; + } + break; + case TraverseProperty.Parameters: + removeIfPresent( + (reflection.parent as SignatureReflection).parameters, + reflection as ParameterReflection, + ); + if ( + !(reflection.parent as SignatureReflection).parameters + ?.length + ) { + delete (reflection.parent as SignatureReflection) + .parameters; + } + break; + case TraverseProperty.SetSignature: + delete parent.setSignature; + break; + case TraverseProperty.Signatures: + removeIfPresent( + parent.signatures, + reflection as SignatureReflection, + ); + if (!parent.signatures?.length) { + delete parent.signatures; + } + break; + case TraverseProperty.TypeLiteral: + parent.type = new IntrinsicType("Object"); + break; + case TraverseProperty.TypeParameter: + removeIfPresent( + parent.typeParameters, + reflection as TypeParameterReflection, + ); + if (!parent.typeParameters?.length) { + delete parent.typeParameters; + } + break; + default: + assertNever(property); } return false; // Stop iteration @@ -211,6 +243,8 @@ export class ProjectReflection extends ContainerReflection { * Remove a reflection without updating the parent reflection to remove references to the removed reflection. */ private _removeReflection(reflection: Reflection) { + this.files.removeReflection(reflection); + // Remove references pointing to this reflection const graph = this.getReferenceGraph(); for (const id of graph.get(reflection.id) ?? []) { @@ -351,6 +385,7 @@ export class ProjectReflection extends ContainerReflection { packageVersion: this.packageVersion, readme: Comment.serializeDisplayParts(serializer, this.readme), symbolIdMap, + files: serializer.toObject(this.files), }; } @@ -365,15 +400,19 @@ export class ProjectReflection extends ContainerReflection { if (obj.readme) { this.readme = Comment.deserializeDisplayParts(de, obj.readme); } + this.files.fromObject(de, obj.files || {}); de.defer(() => { + // Unnecessary conditional in release for (const [id, sid] of Object.entries(obj.symbolIdMap || {})) { const refl = this.getReflectionById(de.oldIdToNewId[+id] ?? -1); if (refl) { this.registerSymbolId(refl, new ReflectionSymbolId(sid)); } else { de.logger.warn( - `Serialized project contained a reflection with id ${id} but it was not present in deserialized project.`, + de.application.i18n.serialized_project_referenced_0_not_part_of_project( + id.toString(), + ), ); } } diff --git a/src/lib/models/reflections/signature.ts b/src/lib/models/reflections/signature.ts index ec4178c6b..d50e88496 100644 --- a/src/lib/models/reflections/signature.ts +++ b/src/lib/models/reflections/signature.ts @@ -1,5 +1,9 @@ -import { SomeType, ReflectionType, ReferenceType } from "../types"; -import { Reflection, TraverseProperty, TraverseCallback } from "./abstract"; +import { type SomeType, ReflectionType, type ReferenceType } from "../types"; +import { + Reflection, + TraverseProperty, + type TraverseCallback, +} from "./abstract"; import type { ParameterReflection } from "./parameter"; import type { TypeParameterReflection } from "./type-parameter"; import type { DeclarationReflection } from "./declaration"; @@ -13,6 +17,8 @@ import { SourceReference } from "../sources/file"; export class SignatureReflection extends Reflection { readonly variant = "signature"; + // ESLint is wrong, we're restricting types to be more narrow. + // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor( name: string, kind: SignatureReflection["kind"], @@ -112,7 +118,7 @@ export class SignatureReflection extends Reflection { ...super.toObject(serializer), variant: this.variant, sources: serializer.toObjectsOptional(this.sources), - typeParameter: serializer.toObjectsOptional(this.typeParameters), + typeParameters: serializer.toObjectsOptional(this.typeParameters), parameters: serializer.toObjectsOptional(this.parameters), type: serializer.toObject(this.type), overwrites: serializer.toObject(this.overwrites), @@ -131,9 +137,15 @@ export class SignatureReflection extends Reflection { obj.sources, (t) => new SourceReference(t.fileName, t.line, t.character), ); - this.typeParameters = de.reviveMany(obj.typeParameter, (t) => - de.constructReflection(t), - ); + if (obj.typeParameter) { + this.typeParameters = de.reviveMany(obj.typeParameter, (t) => + de.constructReflection(t), + ); + } else { + this.typeParameters = de.reviveMany(obj.typeParameters, (t) => + de.constructReflection(t), + ); + } this.parameters = de.reviveMany(obj.parameters, (t) => de.constructReflection(t), ); diff --git a/src/lib/models/reflections/type-parameter.ts b/src/lib/models/reflections/type-parameter.ts index ad5ab8d24..000456b9d 100644 --- a/src/lib/models/reflections/type-parameter.ts +++ b/src/lib/models/reflections/type-parameter.ts @@ -1,5 +1,5 @@ import type { SomeType } from "../types"; -import { Reflection, TraverseCallback } from "./abstract"; +import { Reflection, type TraverseCallback } from "./abstract"; import type { DeclarationReflection } from "./declaration"; import { ReflectionKind } from "./kind"; import type { Serializer, JSONOutput, Deserializer } from "../../serialization"; diff --git a/src/lib/models/reflections/variant.ts b/src/lib/models/reflections/variant.ts index 361a855db..0ffcec521 100644 --- a/src/lib/models/reflections/variant.ts +++ b/src/lib/models/reflections/variant.ts @@ -1,4 +1,5 @@ import type { DeclarationReflection } from "./declaration"; +import type { DocumentReflection } from "./document"; import type { ParameterReflection } from "./parameter"; import type { ProjectReflection } from "./project"; import type { ReferenceReflection } from "./reference"; @@ -16,4 +17,5 @@ export interface ReflectionVariant { reference: ReferenceReflection; signature: SignatureReflection; typeParam: TypeParameterReflection; + document: DocumentReflection; } diff --git a/src/lib/models/types.ts b/src/lib/models/types.ts index be49d3a91..69b64be2c 100644 --- a/src/lib/models/types.ts +++ b/src/lib/models/types.ts @@ -9,6 +9,7 @@ import { ReflectionSymbolId } from "./reflections/ReflectionSymbolId"; import type { DeclarationReference } from "../converter/comments/declarationReference"; import { findPackageForPath } from "../utils/fs"; import { ReflectionKind } from "./reflections/kind"; +import { Comment, type CommentDisplayPart } from "./comments"; /** * Base class of all type definitions. @@ -922,7 +923,7 @@ export class ReferenceType extends Type { symbol.flags & ts.SymbolFlags.TypeParameter ); - const symbolPath = symbol?.declarations?.[0] + const symbolPath = symbol.declarations?.[0] ?.getSourceFile() .fileName.replace(/\\/g, "/"); if (!symbolPath) return ref; @@ -1017,7 +1018,9 @@ export class ReferenceType extends Type { this._target = target.id; } else { de.logger.warn( - `Serialized project contained a reference to ${obj.target} (${this.qualifiedName}), which was not a part of the project.`, + de.application.i18n.serialized_project_referenced_0_not_part_of_project( + JSON.stringify(obj.target), + ), ); } }); @@ -1313,9 +1316,16 @@ export class TypeOperatorType extends Type { export class UnionType extends Type { override readonly type = "union"; + /** + * If present, there should be as many items in this array as there are items in the {@link types} array. + * + * This member is only valid on unions which are on {@link DeclarationReflection.type | DeclarationReflection.type} with a + * {@link ReflectionKind} `kind` of `TypeAlias`. Specifying it on any other union is undefined behavior. + */ + elementSummaries?: CommentDisplayPart[][]; + constructor(public types: SomeType[]) { super(); - this.normalize(); } protected override getTypeString(): string { @@ -1354,31 +1364,10 @@ export class UnionType extends Type { return map[context]; } - private normalize() { - let trueIndex = -1; - let falseIndex = -1; - for ( - let i = 0; - i < this.types.length && (trueIndex === -1 || falseIndex === -1); - i++ - ) { - const t = this.types[i]; - if (t instanceof LiteralType) { - if (t.value === true) { - trueIndex = i; - } - if (t.value === false) { - falseIndex = i; - } - } - } - - if (trueIndex !== -1 && falseIndex !== -1) { - this.types.splice(Math.max(trueIndex, falseIndex), 1); - this.types.splice( - Math.min(trueIndex, falseIndex), - 1, - new IntrinsicType("boolean"), + override fromObject(de: Deserializer, obj: JSONOutput.UnionType): void { + if (obj.elementSummaries) { + this.elementSummaries = obj.elementSummaries.map((parts) => + Comment.deserializeDisplayParts(de, parts), ); } } @@ -1387,6 +1376,9 @@ export class UnionType extends Type { return { type: this.type, types: this.types.map((t) => serializer.toObject(t)), + elementSummaries: this.elementSummaries?.map((parts) => + Comment.serializeDisplayParts(serializer, parts), + ), }; } } diff --git a/src/lib/output/components.ts b/src/lib/output/components.ts index d69087a44..5114ee2de 100644 --- a/src/lib/output/components.ts +++ b/src/lib/output/components.ts @@ -7,10 +7,14 @@ import type { } from "../models/reflections/index"; import type { Renderer } from "./renderer"; import { RendererEvent, PageEvent } from "./events"; +import { Option } from "../utils"; export { Component }; -export abstract class RendererComponent extends AbstractComponent {} +export abstract class RendererComponent extends AbstractComponent< + Renderer, + {} +> {} /** * A plugin for the renderer that reads the current render context. @@ -39,17 +43,25 @@ export abstract class ContextAwareRendererComponent extends RendererComponent { */ protected urlPrefix = /^(http|ftp)s?:\/\//; + private get hostedBaseUrl() { + const url = this.application.options.getValue("hostedBaseUrl"); + return !url || url.endsWith("/") ? url : url + "/"; + } + + @Option("useHostedBaseUrlForAbsoluteLinks") + private accessor useHostedBaseUrlForAbsoluteLinks!: boolean; + /** * Create a new ContextAwareRendererPlugin instance. * * @param renderer The renderer this plugin should be attached to. */ protected override initialize() { - this.listenTo(this.owner, { - [RendererEvent.BEGIN]: this.onBeginRenderer, - [PageEvent.BEGIN]: this.onBeginPage, - [RendererEvent.END]: () => this.absoluteToRelativePathMap.clear(), - }); + this.owner.on(RendererEvent.BEGIN, this.onBeginRenderer.bind(this)); + this.owner.on(PageEvent.BEGIN, this.onBeginPage.bind(this)); + this.owner.on(RendererEvent.END, () => + this.absoluteToRelativePathMap.clear(), + ); } private absoluteToRelativePathMap = new Map(); @@ -67,7 +79,13 @@ export abstract class ContextAwareRendererComponent extends RendererComponent { const key = `${this.location}:${absolute}`; let path = this.absoluteToRelativePathMap.get(key); if (path) return path; - path = Path.posix.relative(this.location, absolute) || "."; + + if (this.useHostedBaseUrlForAbsoluteLinks) { + path = new URL(absolute, this.hostedBaseUrl).toString(); + } else { + path = Path.posix.relative(this.location, absolute) || "."; + } + this.absoluteToRelativePathMap.set(key, path); return path; } diff --git a/src/lib/output/events.ts b/src/lib/output/events.ts index 8ab246b86..8431db0ad 100644 --- a/src/lib/output/events.ts +++ b/src/lib/output/events.ts @@ -1,10 +1,10 @@ import * as Path from "path"; -import { Event } from "../utils/events"; import type { ProjectReflection } from "../models/reflections/project"; import type { RenderTemplate, UrlMapping } from "./models/UrlMapping"; import type { DeclarationReflection, + DocumentReflection, Reflection, ReflectionKind, } from "../models"; @@ -16,7 +16,7 @@ import type { * @see {@link Renderer.EVENT_BEGIN} * @see {@link Renderer.EVENT_END} */ -export class RendererEvent extends Event { +export class RendererEvent { /** * The project the renderer is currently processing. */ @@ -46,12 +46,7 @@ export class RendererEvent extends Event { */ static readonly END = "endRender"; - constructor( - name: string, - outputDirectory: string, - project: ProjectReflection, - ) { - super(name); + constructor(outputDirectory: string, project: ProjectReflection) { this.outputDirectory = outputDirectory; this.project = project; } @@ -74,6 +69,14 @@ export class RendererEvent extends Event { } } +export interface PageHeading { + link: string; + text: string; + level?: number; + kind?: ReflectionKind; + classes?: string; +} + /** * An event emitted by the {@link Renderer} class before and after the * markup of a page is rendered. @@ -113,13 +116,29 @@ export class PageEvent extends Event { * Links to content within this page that should be rendered in the page navigation. * This is built when rendering the document content. */ - pageHeadings: Array<{ - link: string; - text: string; - level?: number; - kind?: ReflectionKind; - classes?: string; - }> = []; + pageHeadings: PageHeading[] = []; + + /** + * Sections of the page, generally set by `@group`s + */ + pageSections = [ + { + title: "", + headings: this.pageHeadings, + }, + ]; + + /** + * Start a new section of the page. Sections are collapsible within + * the "On This Page" sidebar. + */ + startNewSection(title: string) { + this.pageHeadings = []; + this.pageSections.push({ + title, + headings: this.pageHeadings, + }); + } /** * Triggered before a document will be rendered. @@ -143,9 +162,8 @@ export class PageEvent extends Event { * An event emitted when markdown is being parsed. Allows other plugins to manipulate the result. * * @see {@link MarkdownEvent.PARSE} - * @see {@link MarkdownEvent.INCLUDE} */ -export class MarkdownEvent extends Event { +export class MarkdownEvent { /** * The unparsed original text. */ @@ -167,19 +185,7 @@ export class MarkdownEvent extends Event { */ static readonly PARSE = "parseMarkdown"; - /** - * Triggered on the renderer when this plugin includes a markdown file through a markdown include tag. - * @event - */ - static readonly INCLUDE = "includeMarkdown"; - - constructor( - name: string, - page: PageEvent, - originalText: string, - parsedText: string, - ) { - super(name); + constructor(page: PageEvent, originalText: string, parsedText: string) { this.page = page; this.originalText = originalText; this.parsedText = parsedText; @@ -189,7 +195,7 @@ export class MarkdownEvent extends Event { /** * An event emitted when the search index is being prepared. */ -export class IndexEvent extends Event { +export class IndexEvent { /** * Triggered on the renderer when the search index is being prepared. * @event @@ -204,11 +210,11 @@ export class IndexEvent extends Event { * same index from {@link searchFields}. The {@link removeResult} helper * will do this for you. */ - searchResults: DeclarationReflection[]; + searchResults: Array; /** * Additional search fields to be used when creating the search index. - * `name` and `comment` may be specified to overwrite TypeDoc's search fields. + * `name`, `comment` and `document` may be specified to overwrite TypeDoc's search fields. * * Do not use `id` as a custom search field. */ @@ -216,7 +222,7 @@ export class IndexEvent extends Event { /** * Weights for the fields defined in `searchFields`. The default will weight - * `name` as 10x more important than comment content. + * `name` as 10x more important than comment and document content. * * If a field added to {@link searchFields} is not added to this object, it * will **not** be searchable. @@ -227,6 +233,7 @@ export class IndexEvent extends Event { readonly searchFieldWeights: Record = { name: 10, comment: 1, + document: 1, }; /** @@ -237,8 +244,9 @@ export class IndexEvent extends Event { this.searchFields.splice(index, 1); } - constructor(name: string, searchResults: DeclarationReflection[]) { - super(name); + constructor( + searchResults: Array, + ) { this.searchResults = searchResults; this.searchFields = Array.from( { length: this.searchResults.length }, diff --git a/src/lib/output/index.ts b/src/lib/output/index.ts index 9e63faa68..9046992b3 100644 --- a/src/lib/output/index.ts +++ b/src/lib/output/index.ts @@ -1,11 +1,12 @@ export { PageEvent, RendererEvent, MarkdownEvent, IndexEvent } from "./events"; export { UrlMapping } from "./models/UrlMapping"; export type { RenderTemplate } from "./models/UrlMapping"; -export { Renderer } from "./renderer"; +export { Renderer, type RendererEvents } from "./renderer"; export type { RendererHooks } from "./renderer"; export { Theme } from "./theme"; export { DefaultTheme, + Slugger, type NavigationElement, } from "./themes/default/DefaultTheme"; export { DefaultThemeRenderContext } from "./themes/default/DefaultThemeRenderContext"; diff --git a/src/lib/output/models/UrlMapping.ts b/src/lib/output/models/UrlMapping.ts index 3d75b152c..562a267ea 100644 --- a/src/lib/output/models/UrlMapping.ts +++ b/src/lib/output/models/UrlMapping.ts @@ -19,4 +19,8 @@ export class UrlMapping { } } +/** + * @param data the reflection to render + * @returns either a string to be written to the file, or an element to be serialized and then written. + */ export type RenderTemplate = (data: T) => JSX.Element | string; diff --git a/src/lib/output/plugins/AssetsPlugin.ts b/src/lib/output/plugins/AssetsPlugin.ts index 98fa1a97b..d93daf928 100644 --- a/src/lib/output/plugins/AssetsPlugin.ts +++ b/src/lib/output/plugins/AssetsPlugin.ts @@ -1,6 +1,6 @@ import { Component, RendererComponent } from "../components"; import { RendererEvent } from "../events"; -import { copySync, writeFileSync } from "../../utils/fs"; +import { copySync, readFile, writeFileSync } from "../../utils/fs"; import { DefaultTheme } from "../themes/default/DefaultTheme"; import { getStyles } from "../../utils/highlighter"; import { Option } from "../../utils"; @@ -17,27 +17,36 @@ export class AssetsPlugin extends RendererComponent { @Option("customCss") accessor customCss!: string; + getTranslatedStrings() { + return { + copy: this.application.i18n.theme_copy(), + copied: this.application.i18n.theme_copied(), + normally_hidden: this.application.i18n.theme_normally_hidden(), + }; + } + /** * Create a new AssetsPlugin instance. */ override initialize() { - this.listenTo(this.owner, { - [RendererEvent.END]: this.onRenderEnd, - [RendererEvent.BEGIN]: (event: RendererEvent) => { - const dest = join(event.outputDirectory, "assets"); - - if (this.customCss) { - if (existsSync(this.customCss)) { - copySync(this.customCss, join(dest, "custom.css")); - } else { - this.application.logger.error( - `Custom CSS file at ${this.customCss} does not exist.`, - ); - event.preventDefault(); - } - } - }, - }); + this.owner.on(RendererEvent.BEGIN, this.onRenderBegin.bind(this)); + this.owner.on(RendererEvent.END, this.onRenderEnd.bind(this)); + } + + private onRenderBegin(event: RendererEvent) { + const dest = join(event.outputDirectory, "assets"); + + if (this.customCss) { + if (existsSync(this.customCss)) { + copySync(this.customCss, join(dest, "custom.css")); + } else { + this.application.logger.error( + this.application.i18n.custom_css_file_0_does_not_exist( + this.customCss, + ), + ); + } + } } /** @@ -49,9 +58,25 @@ export class AssetsPlugin extends RendererComponent { if (this.owner.theme instanceof DefaultTheme) { const src = join(__dirname, "..", "..", "..", "..", "static"); const dest = join(event.outputDirectory, "assets"); - copySync(src, dest); + copySync(join(src, "style.css"), join(dest, "style.css")); + + const mainJs = readFile(join(src, "main.js")); + writeFileSync( + join(dest, "main.js"), + [ + '"use strict";', + `window.translations=${JSON.stringify(this.getTranslatedStrings())};`, + mainJs, + ].join("\n"), + ); writeFileSync(join(dest, "highlight.css"), getStyles()); + + const media = join(event.outputDirectory, "media"); + const toCopy = event.project.files.getNameToAbsoluteMap(); + for (const [fileName, absolute] of toCopy.entries()) { + copySync(absolute, join(media, fileName)); + } } } } diff --git a/src/lib/output/plugins/IconsPlugin.tsx b/src/lib/output/plugins/IconsPlugin.tsx index 93fe66674..84c419778 100644 --- a/src/lib/output/plugins/IconsPlugin.tsx +++ b/src/lib/output/plugins/IconsPlugin.tsx @@ -5,6 +5,27 @@ import { DefaultTheme } from "../themes/default/DefaultTheme"; import { join } from "path"; import { JSX, renderElement } from "../../utils"; +const ICONS_JS = ` +(function() { + addIcons(); + function addIcons() { + if (document.readyState === "loading") return document.addEventListener("DOMContentLoaded", addIcons); + const svg = document.body.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg")); + svg.innerHTML = \`"SVG_HTML"\`; + svg.style.display = "none"; + if (location.protocol === "file:") updateUseElements(); + } + + function updateUseElements() { + document.querySelectorAll("use").forEach(el => { + if (el.getAttribute("href").includes("#icon-")) { + el.setAttribute("href", el.getAttribute("href").replace(/.*#/, "#")); + } + }); + } +})() +`.trim(); + /** * Plugin which is responsible for creating an icons.js file that embeds the icon SVGs * within the page on page load to reduce page sizes. @@ -14,9 +35,7 @@ export class IconsPlugin extends RendererComponent { iconHtml?: string; override initialize() { - this.listenTo(this.owner, { - [RendererEvent.BEGIN]: this.onBeginRender, - }); + this.owner.on(RendererEvent.BEGIN, this.onBeginRender.bind(this)); } private onBeginRender(_event: RendererEvent) { @@ -34,23 +53,7 @@ export class IconsPlugin extends RendererComponent { } const svg = renderElement({children}); - const js = [ - "(function(svg) {", - " svg.innerHTML = `" + renderElement(<>{children}).replaceAll("`", "\\`") + "`;", - " svg.style.display = 'none';", - " if (location.protocol === 'file:') {", - " if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateUseElements);", - " else updateUseElements()", - " function updateUseElements() {", - " document.querySelectorAll('use').forEach(el => {", - " if (el.getAttribute('href').includes('#icon-')) {", - " el.setAttribute('href', el.getAttribute('href').replace(/.*#/, '#'));", - " }", - " });", - " }", - " }", - "})(document.body.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg')))", - ].join("\n"); + const js = ICONS_JS.replace("SVG_HTML", renderElement(<>{children}).replaceAll("`", "\\`")); const svgPath = join(event.outputDirectory, "assets/icons.svg"); const jsPath = join(event.outputDirectory, "assets/icons.js"); diff --git a/src/lib/output/plugins/JavascriptIndexPlugin.ts b/src/lib/output/plugins/JavascriptIndexPlugin.ts index 027dc3ea2..e553a71cd 100644 --- a/src/lib/output/plugins/JavascriptIndexPlugin.ts +++ b/src/lib/output/plugins/JavascriptIndexPlugin.ts @@ -2,9 +2,11 @@ import * as Path from "path"; import { Builder, trimmer } from "lunr"; import { - Comment, + type Comment, DeclarationReflection, + DocumentReflection, ProjectReflection, + type Reflection, } from "../../models"; import { Component, RendererComponent } from "../components"; import { IndexEvent, RendererEvent } from "../events"; @@ -34,27 +36,19 @@ interface SearchDocument { @Component({ name: "javascript-index" }) export class JavascriptIndexPlugin extends RendererComponent { @Option("searchInComments") - accessor searchComments!: boolean; + private accessor searchComments!: boolean; + + @Option("searchInDocuments") + private accessor searchDocuments!: boolean; - /** - * Create a new JavascriptIndexPlugin instance. - */ override initialize() { - this.listenTo(this.owner, RendererEvent.BEGIN, this.onRendererBegin); + this.owner.on(RendererEvent.BEGIN, this.onRendererBegin.bind(this)); } - /** - * Triggered after a document has been rendered, just before it is written to disc. - * - * @param event An event object describing the current render operation. - */ - private onRendererBegin(event: RendererEvent) { + private onRendererBegin(_event: RendererEvent) { if (!(this.owner.theme instanceof DefaultTheme)) { return; } - if (event.isDefaultPrevented) { - return; - } this.owner.preRenderAsyncJobs.push((event) => this.buildSearchIndex(event), @@ -70,23 +64,17 @@ export class JavascriptIndexPlugin extends RendererComponent { event.project.reflections, ).filter((refl) => { return ( - refl instanceof DeclarationReflection && + (refl instanceof DeclarationReflection || + refl instanceof DocumentReflection) && refl.url && refl.name && !refl.flags.isExternal ); - }) as DeclarationReflection[]; + }) as Array; - const indexEvent = new IndexEvent( - IndexEvent.PREPARE_INDEX, - initialSearchResults, - ); + const indexEvent = new IndexEvent(initialSearchResults); - this.owner.trigger(indexEvent); - - if (indexEvent.isDefaultPrevented) { - return; - } + this.owner.trigger(IndexEvent.PREPARE_INDEX, indexEvent); const builder = new Builder(); builder.pipeline.add(trimmer); @@ -128,6 +116,7 @@ export class JavascriptIndexPlugin extends RendererComponent { { name: reflection.name, comment: this.getCommentSearchText(reflection), + document: this.getDocumentSearchText(reflection), ...indexEvent.searchFields[rows.length], id: rows.length, }, @@ -158,18 +147,20 @@ export class JavascriptIndexPlugin extends RendererComponent { ); } - private getCommentSearchText(reflection: DeclarationReflection) { + private getCommentSearchText(reflection: Reflection) { if (!this.searchComments) return; const comments: Comment[] = []; if (reflection.comment) comments.push(reflection.comment); - reflection.signatures?.forEach( - (s) => s.comment && comments.push(s.comment), - ); - reflection.getSignature?.comment && - comments.push(reflection.getSignature.comment); - reflection.setSignature?.comment && - comments.push(reflection.setSignature.comment); + if (reflection.isDeclaration()) { + reflection.signatures?.forEach( + (s) => s.comment && comments.push(s.comment), + ); + reflection.getSignature?.comment && + comments.push(reflection.getSignature.comment); + reflection.setSignature?.comment && + comments.push(reflection.setSignature.comment); + } if (!comments.length) { return; @@ -182,4 +173,12 @@ export class JavascriptIndexPlugin extends RendererComponent { .map((part) => part.text) .join("\n"); } + + private getDocumentSearchText(reflection: Reflection) { + if (!this.searchDocuments) return; + + if (reflection.isDocument()) { + return reflection.content.flatMap((c) => c.text).join("\n"); + } + } } diff --git a/src/lib/output/plugins/NavigationPlugin.ts b/src/lib/output/plugins/NavigationPlugin.ts index 595110da0..2056935dc 100644 --- a/src/lib/output/plugins/NavigationPlugin.ts +++ b/src/lib/output/plugins/NavigationPlugin.ts @@ -11,17 +11,13 @@ const gzipP = promisify(gzip); @Component({ name: "navigation-tree" }) export class NavigationPlugin extends RendererComponent { override initialize() { - this.listenTo(this.owner, RendererEvent.BEGIN, this.onRendererBegin); + this.owner.on(RendererEvent.BEGIN, this.onRendererBegin.bind(this)); } - private onRendererBegin(event: RendererEvent) { + private onRendererBegin(_event: RendererEvent) { if (!(this.owner.theme instanceof DefaultTheme)) { return; } - if (event.isDefaultPrevented) { - return; - } - this.owner.preRenderAsyncJobs.push((event) => this.buildNavigationIndex(event), ); diff --git a/src/lib/output/plugins/SitemapPlugin.ts b/src/lib/output/plugins/SitemapPlugin.ts index dff37af31..5fd8622c7 100644 --- a/src/lib/output/plugins/SitemapPlugin.ts +++ b/src/lib/output/plugins/SitemapPlugin.ts @@ -2,26 +2,40 @@ import Path from "path"; import { Component, RendererComponent } from "../components"; import { RendererEvent } from "../events"; import { DefaultTheme } from "../themes/default/DefaultTheme"; -import { Option, writeFile } from "../../utils"; +import { writeFile } from "../../utils"; import { escapeHtml } from "../../utils/html"; +import { Fragment } from "../../utils/jsx"; @Component({ name: "sitemap" }) export class SitemapPlugin extends RendererComponent { - @Option("sitemapBaseUrl") - accessor sitemapBaseUrl!: string; + private get hostedBaseUrl() { + const url = this.application.options.getValue("hostedBaseUrl"); + return !url || url.endsWith("/") ? url : url + "/"; + } override initialize() { - this.listenTo(this.owner, RendererEvent.BEGIN, this.onRendererBegin); + this.owner.on(RendererEvent.BEGIN, this.onRendererBegin.bind(this)); } - private onRendererBegin(event: RendererEvent) { + private onRendererBegin(_event: RendererEvent) { if (!(this.owner.theme instanceof DefaultTheme)) { return; } - if (event.isDefaultPrevented || !this.sitemapBaseUrl) { + if (!this.hostedBaseUrl) { return; } + this.owner.hooks.on("head.begin", (context) => { + if (context.page.url === "index.html") { + return { + tag: "link", + props: { rel: "canonical", href: this.hostedBaseUrl }, + children: [], + }; + } + return { tag: Fragment, props: null, children: [] }; + }); + this.owner.preRenderAsyncJobs.push((event) => this.buildSitemap(event)); } @@ -39,7 +53,7 @@ export class SitemapPlugin extends RendererComponent { tag: "loc", children: new URL( url.url, - this.sitemapBaseUrl, + this.hostedBaseUrl, ).toString(), }, { diff --git a/src/lib/output/renderer.ts b/src/lib/output/renderer.ts index 3ec4aa27d..c64020742 100644 --- a/src/lib/output/renderer.ts +++ b/src/lib/output/renderer.ts @@ -11,7 +11,12 @@ import * as path from "path"; import type { Application } from "../application"; import type { Theme } from "./theme"; -import { RendererEvent, PageEvent, IndexEvent } from "./events"; +import { + RendererEvent, + PageEvent, + IndexEvent, + type MarkdownEvent, +} from "./events"; import type { ProjectReflection } from "../models/reflections/project"; import type { RenderTemplate } from "./models/UrlMapping"; import { writeFileSync } from "../utils/fs"; @@ -20,8 +25,11 @@ import { RendererComponent } from "./components"; import { Component, ChildableComponent } from "../utils/component"; import { Option, EventHooks } from "../utils"; import { loadHighlighter } from "../utils/highlighter"; -import type { Theme as ShikiTheme } from "shiki"; -import { Reflection } from "../models"; +import type { + BundledLanguage, + BundledTheme as ShikiTheme, +} from "shiki" with { "resolution-mode": "import" }; +import { type Comment, Reflection } from "../models"; import type { JsxElement } from "../utils/jsx.elements"; import type { DefaultThemeRenderContext } from "./themes/default/DefaultThemeRenderContext"; import { validateStateIsClean } from "./themes/default/partials/type"; @@ -91,6 +99,33 @@ export interface RendererHooks { * Applied immediately after the "Generated by TypeDoc" link in the footer. */ "footer.end": [DefaultThemeRenderContext]; + + /** + * Applied immediately before a comment's tags are rendered. + * + * This may be used to set {@link Models.CommentTag.skipRendering} on any tags which + * should not be rendered. + */ + "comment.beforeTags": [DefaultThemeRenderContext, Comment, Reflection]; + + /** + * Applied immediately after a comment's tags are rendered. + * + * This may be used to set {@link Models.CommentTag.skipRendering} on any tags which + * should not be rendered as this hook is called before the tags are actually + * rendered. + */ + "comment.afterTags": [DefaultThemeRenderContext, Comment, Reflection]; +} + +export interface RendererEvents { + beginRender: [RendererEvent]; + beginPage: [PageEvent]; + endPage: [PageEvent]; + endRender: [RendererEvent]; + + parseMarkdown: [MarkdownEvent]; + prepareIndex: [IndexEvent]; } /** @@ -104,8 +139,7 @@ export interface RendererHooks { * * * {@link Renderer.EVENT_BEGIN}
* Triggered before the renderer starts rendering a project. The listener receives - * an instance of {@link RendererEvent}. By calling {@link RendererEvent.preventDefault} the entire - * render process can be canceled. + * an instance of {@link RendererEvent}. * * * {@link Renderer.EVENT_BEGIN_PAGE}
* Triggered before a document will be rendered. The listener receives an instance of @@ -124,11 +158,14 @@ export interface RendererHooks { * * {@link Renderer.EVENT_PREPARE_INDEX}
* Triggered when the JavascriptIndexPlugin is preparing the search index. Listeners receive * an instance of {@link IndexEvent}. + * + * @document ../../../internal-docs/custom-themes.md */ @Component({ name: "renderer", internal: true, childClass: RendererComponent }) export class Renderer extends ChildableComponent< Application, - RendererComponent + RendererComponent, + RendererEvents > { private themes = new Map Theme>([ ["default", DefaultTheme], @@ -185,35 +222,32 @@ export class Renderer extends ChildableComponent< /** @internal */ @Option("theme") - accessor themeName!: string; + private accessor themeName!: string; - /** @internal */ @Option("cleanOutputDir") - accessor cleanOutputDir!: boolean; + private accessor cleanOutputDir!: boolean; - /** @internal */ @Option("cname") - accessor cname!: string; + private accessor cname!: string; - /** @internal */ @Option("githubPages") - accessor githubPages!: boolean; + private accessor githubPages!: boolean; /** @internal */ @Option("cacheBust") accessor cacheBust!: boolean; - /** @internal */ @Option("lightHighlightTheme") - accessor lightTheme!: ShikiTheme; + private accessor lightTheme!: ShikiTheme; - /** @internal */ @Option("darkHighlightTheme") - accessor darkTheme!: ShikiTheme; + private accessor darkTheme!: ShikiTheme; + + @Option("highlightLanguages") + private accessor highlightLanguages!: string[]; - /** @internal */ @Option("pretty") - accessor pretty!: boolean; + private accessor pretty!: boolean; renderStartTime = -1; @@ -253,32 +287,24 @@ export class Renderer extends ChildableComponent< return; } - const output = new RendererEvent( - RendererEvent.BEGIN, - outputDirectory, - project, - ); + const output = new RendererEvent(outputDirectory, project); output.urls = this.theme!.getUrls(project); - this.trigger(output); + this.trigger(RendererEvent.BEGIN, output); await this.runPreRenderJobs(output); - if (!output.isDefaultPrevented) { - this.application.logger.verbose( - `There are ${output.urls.length} pages to write.`, - ); - output.urls.forEach((mapping) => { - this.renderDocument(...output.createPageEvent(mapping)); - validateStateIsClean(mapping.url); - }); + this.application.logger.verbose( + `There are ${output.urls.length} pages to write.`, + ); + output.urls.forEach((mapping) => { + this.renderDocument(...output.createPageEvent(mapping)); + validateStateIsClean(mapping.url); + }); - await Promise.all( - this.postRenderAsyncJobs.map((job) => job(output)), - ); - this.postRenderAsyncJobs = []; + await Promise.all(this.postRenderAsyncJobs.map((job) => job(output))); + this.postRenderAsyncJobs = []; - this.trigger(RendererEvent.END, output); - } + this.trigger(RendererEvent.END, output); this.theme = void 0; this.hooks.restoreMomento(momento); @@ -297,7 +323,12 @@ export class Renderer extends ChildableComponent< } private async loadHighlighter() { - await loadHighlighter(this.lightTheme, this.darkTheme); + await loadHighlighter( + this.lightTheme, + this.darkTheme, + // Checked in option validation + this.highlightLanguages as BundledLanguage[], + ); } /** @@ -312,10 +343,6 @@ export class Renderer extends ChildableComponent< ) { const momento = this.hooks.saveMomento(); this.trigger(PageEvent.BEGIN, page); - if (page.isDefaultPrevented) { - this.hooks.restoreMomento(momento); - return false; - } if (page.model instanceof Reflection) { page.contents = this.theme!.render(page, template); @@ -326,14 +353,12 @@ export class Renderer extends ChildableComponent< this.trigger(PageEvent.END, page); this.hooks.restoreMomento(momento); - if (page.isDefaultPrevented) { - return false; - } - try { writeFileSync(page.filename, page.contents); } catch (error) { - this.application.logger.error(`Could not write ${page.filename}`); + this.application.logger.error( + this.application.i18n.could_not_write_0(page.filename), + ); } } @@ -350,11 +375,10 @@ export class Renderer extends ChildableComponent< const ctor = this.themes.get(this.themeName); if (!ctor) { this.application.logger.error( - `The theme '${ - this.themeName - }' is not defined. The available themes are: ${[ - ...this.themes.keys(), - ].join(", ")}`, + this.application.i18n.theme_0_is_not_defined_available_are_1( + this.themeName, + [...this.themes.keys()].join(", "), + ), ); return false; } else { @@ -381,7 +405,9 @@ export class Renderer extends ChildableComponent< }); } catch (error) { this.application.logger.warn( - "Could not empty the output directory.", + this.application.i18n.could_not_empty_output_directory_0( + directory, + ), ); return false; } @@ -391,7 +417,9 @@ export class Renderer extends ChildableComponent< fs.mkdirSync(directory, { recursive: true }); } catch (error) { this.application.logger.error( - `Could not create output directory ${directory}.`, + this.application.i18n.could_not_create_output_directory_0( + directory, + ), ); return false; } @@ -406,7 +434,9 @@ export class Renderer extends ChildableComponent< fs.writeFileSync(path.join(directory, ".nojekyll"), text); } catch (error) { this.application.logger.warn( - "Could not create .nojekyll file.", + this.application.i18n.could_not_write_0( + path.join(directory, ".nojekyll"), + ), ); return false; } diff --git a/src/lib/output/theme.ts b/src/lib/output/theme.ts index 1f9324512..1fd847079 100644 --- a/src/lib/output/theme.ts +++ b/src/lib/output/theme.ts @@ -16,15 +16,6 @@ import type { Reflection } from "../models"; */ @Component({ name: "theme", internal: true }) export abstract class Theme extends RendererComponent { - /** - * Create a new BaseTheme instance. - * - * @param renderer The renderer this theme is attached to. - */ - constructor(renderer: Renderer) { - super(renderer); - } - /** * Map the models of the given project to the desired output files. * It is assumed that with the project structure: diff --git a/src/lib/output/themes/MarkedPlugin.tsx b/src/lib/output/themes/MarkedPlugin.tsx index 6d201537a..329e1185a 100644 --- a/src/lib/output/themes/MarkedPlugin.tsx +++ b/src/lib/output/themes/MarkedPlugin.tsx @@ -1,13 +1,25 @@ -import * as fs from "fs"; -import * as Path from "path"; -import * as Marked from "marked"; +import markdown from "markdown-it"; import { Component, ContextAwareRendererComponent } from "../components"; -import { RendererEvent, MarkdownEvent, PageEvent } from "../events"; -import { Option, readFile, copySync, isFile, JSX, renderElement } from "../../utils"; -import { highlight, isSupportedLanguage } from "../../utils/highlighter"; -import type { Theme } from "shiki"; -import { escapeHtml, getTextContent } from "../../utils/html"; +import { type RendererEvent, MarkdownEvent, type PageEvent } from "../events"; +import { Option, type Logger, renderElement, assertNever } from "../../utils"; +import { highlight, isLoadedLanguage, isSupportedLanguage } from "../../utils/highlighter"; +import type { BundledTheme } from "shiki" with { "resolution-mode": "import" }; +import { escapeHtml } from "../../utils/html"; +import type { DefaultTheme } from ".."; +import { Slugger } from "./default/DefaultTheme"; +import { anchorIcon } from "./default/partials/anchor-icon"; +import type { DefaultThemeRenderContext } from ".."; +import { ReflectionKind, type CommentDisplayPart } from "../../models"; + +let defaultSlugger: Slugger | undefined; +function getDefaultSlugger(logger: Logger) { + if (!defaultSlugger) { + logger.warn(logger.i18n.custom_theme_does_not_define_getSlugger()); + defaultSlugger = new Slugger(); + } + return defaultSlugger; +} /** * Implements markdown and relativeURL helpers for templates. @@ -15,47 +27,30 @@ import { escapeHtml, getTextContent } from "../../utils/html"; */ @Component({ name: "marked" }) export class MarkedPlugin extends ContextAwareRendererComponent { - @Option("includes") - accessor includeSource!: string; - - @Option("media") - accessor mediaSource!: string; - @Option("lightHighlightTheme") - accessor lightTheme!: Theme; + accessor lightTheme!: BundledTheme; @Option("darkHighlightTheme") - accessor darkTheme!: Theme; + accessor darkTheme!: BundledTheme; - /** - * The path referenced files are located in. - */ - private includes?: string; + @Option("markdownItOptions") + accessor markdownItOptions!: Record; - /** - * Path to the output media directory. - */ - private mediaDirectory?: string; - - /** - * The pattern used to find references in markdown. - */ - private includePattern = /\[\[include:([^\]]+?)\]\]/g; + private parser?: markdown; /** - * The pattern used to find media links. + * This needing to be here really feels hacky... probably some nicer way to do this. + * Revisit when adding support for arbitrary pages in 0.26. */ - private mediaPattern = /media:\/\/([^ ")\]}]+)/g; - - private sources?: { fileName: string; line: number }[]; - private outputFileName?: string; + private renderContext: DefaultThemeRenderContext = null!; + private lastHeaderSlug = ""; /** * Create a new MarkedPlugin instance. */ override initialize() { super.initialize(); - this.listenTo(this.owner, MarkdownEvent.PARSE, this.onParseMarkdown); + this.owner.on(MarkdownEvent.PARSE, this.onParseMarkdown.bind(this)); } /** @@ -69,14 +64,21 @@ export class MarkedPlugin extends ContextAwareRendererComponent { lang = lang || "typescript"; lang = lang.toLowerCase(); if (!isSupportedLanguage(lang)) { - // Extra newline because of the progress bar - this.application.logger.warn(` -Unsupported highlight language "${lang}" will not be highlighted. Run typedoc --help for a list of supported languages. -target code block : -\t${text.split("\n").join("\n\t")} -source files :${this.sources?.map((source) => `\n\t${source.fileName}`).join()} -output file : -\t${this.outputFileName}`); + this.application.logger.warn( + this.application.i18n.unsupported_highlight_language_0_not_highlighted_in_comment_for_1( + lang, + this.page?.model.getFriendlyFullName() ?? "(unknown)", + ), + ); + return text; + } + if (!isLoadedLanguage(lang)) { + this.application.logger.warn( + this.application.i18n.unloaded_language_0_not_highlighted_in_comment_for_1( + lang, + this.page?.model.getFriendlyFullName() ?? "(unknown)", + ), + ); return text; } @@ -86,42 +88,111 @@ output file : /** * Parse the given markdown string and return the resulting html. * - * @param text The markdown string that should be parsed. + * @param input The markdown string that should be parsed. * @returns The resulting html string. */ - public parseMarkdown(text: string, page: PageEvent) { - if (this.includes) { - text = text.replace(this.includePattern, (_match, path) => { - path = Path.join(this.includes!, path.trim()); - if (isFile(path)) { - const contents = readFile(path); - const event = new MarkdownEvent(MarkdownEvent.INCLUDE, page, contents, contents); - this.owner.trigger(event); - return event.parsedText; - } else { - this.application.logger.warn("Could not find file to include: " + path); - return ""; - } - }); + public parseMarkdown( + input: string | readonly CommentDisplayPart[], + page: PageEvent, + context: DefaultThemeRenderContext, + ) { + let markdown = input; + if (typeof markdown !== "string") { + markdown = this.displayPartsToMarkdown(page, context, markdown); } - if (this.mediaDirectory) { - text = text.replace(this.mediaPattern, (match: string, path: string) => { - const fileName = Path.join(this.mediaDirectory!, path); + this.renderContext = context; + const event = new MarkdownEvent(page, markdown, markdown); - if (isFile(fileName)) { - return this.getRelativeUrl("media") + "/" + path; - } else { - this.application.logger.warn("Could not find media file: " + fileName); - return match; - } - }); - } + this.owner.trigger(MarkdownEvent.PARSE, event); + this.renderContext = null!; + return event.parsedText; + } - const event = new MarkdownEvent(MarkdownEvent.PARSE, page, text, text); + private displayPartsToMarkdown( + page: PageEvent, + context: DefaultThemeRenderContext, + parts: readonly CommentDisplayPart[], + ): string { + const useHtml = !!this.markdownItOptions["html"]; + const result: string[] = []; + + for (const part of parts) { + switch (part.kind) { + case "text": + case "code": + result.push(part.text); + break; + case "inline-tag": + switch (part.tag) { + case "@label": + case "@inheritdoc": // Shouldn't happen + break; // Not rendered. + case "@link": + case "@linkcode": + case "@linkplain": { + if (part.target) { + let url: string | undefined; + let kindClass: string | undefined; + if (typeof part.target === "string") { + url = part.target; + } else if ("id" in part.target) { + // No point in trying to resolve a ReflectionSymbolId at this point, we've already + // tried and failed during the resolution step. + url = context.urlTo(part.target); + kindClass = ReflectionKind.classString(part.target.kind); + } + + if (useHtml) { + const text = part.tag === "@linkcode" ? `${part.text}` : part.text; + result.push( + url + ? `${text}` + : part.text, + ); + } else { + const text = part.tag === "@linkcode" ? "`" + part.text + "`" : part.text; + result.push(url ? `[${text}](${url})` : text); + } + } else { + result.push(part.text); + } + break; + } + default: + // Hmm... probably want to be able to render these somehow, so custom inline tags can be given + // special rendering rules. Future capability. For now, just render their text. + result.push(`{${part.tag} ${part.text}}`); + break; + } + break; + case "relative-link": + switch (typeof part.target) { + case "number": { + const refl = page.project.files.resolve(part.target); + if (typeof refl === "object") { + result.push(context.urlTo(refl)); + break; + } + + const fileName = page.project.files.getName(part.target); + if (fileName) { + result.push(context.relativeURL(`media/${fileName}`)); + break; + } + } + // fall through + case "undefined": + result.push(part.text); + break; + } + break; + default: + assertNever(part); + } + } - this.owner.trigger(event); - return event.parsedText; + return result.join(""); } /** @@ -131,27 +202,14 @@ output file : */ protected override onBeginRenderer(event: RendererEvent) { super.onBeginRenderer(event); + this.setupParser(); + } - Marked.marked.setOptions(this.createMarkedOptions()); - - delete this.includes; - if (this.includeSource) { - if (fs.existsSync(this.includeSource) && fs.statSync(this.includeSource).isDirectory()) { - this.includes = this.includeSource; - } else { - this.application.logger.warn("Could not find provided includes directory: " + this.includeSource); - } - } - - if (this.mediaSource) { - if (fs.existsSync(this.mediaSource) && fs.statSync(this.mediaSource).isDirectory()) { - this.mediaDirectory = Path.join(event.outputDirectory, "media"); - copySync(this.mediaSource, this.mediaDirectory); - } else { - this.mediaDirectory = undefined; - this.application.logger.warn("Could not find provided media directory: " + this.mediaSource); - } + private getSlugger() { + if ("getSlugger" in this.owner.theme!) { + return (this.owner.theme as DefaultTheme).getSlugger(this.page!.model); } + return getDefaultSlugger(this.application.logger); } /** @@ -159,51 +217,56 @@ output file : * * @returns The options object for the markdown parser. */ - private createMarkedOptions(): Marked.marked.MarkedOptions { - const markedOptions = (this.application.options.getValue("markedOptions") ?? {}) as Marked.marked.MarkedOptions; - - // Set some default values if they are not specified via the TypeDoc option - markedOptions.highlight ??= (text, lang) => this.getHighlighted(text, lang); - - if (!markedOptions.renderer) { - markedOptions.renderer = new Marked.Renderer(); - - markedOptions.renderer.link = (href, title, text) => { - // Prefix the #anchor links `#md:`. - const target = href?.replace(/^#(?:md:)?(.+)/, "#md:$1") || undefined; - return renderElement( - - - , - ); - }; - - markedOptions.renderer.heading = (text, level, _, slugger) => { - const slug = slugger.slug(text); - // Prefix the slug with an extra `md:` to prevent conflicts with TypeDoc's anchors. - this.page!.pageHeadings.push({ - link: `#md:${slug}`, - text: getTextContent(text), - level, - }); - const H = `h${level}`; - return renderElement( - <> - - - - - - - , - ); - }; - markedOptions.renderer.code = renderCode; - } + private setupParser() { + this.parser = markdown({ + ...this.markdownItOptions, + highlight: (code, lang) => { + code = this.getHighlighted(code, lang || "ts"); + code = code.replace(/\n$/, "") + "\n"; + + if (!lang) { + return `
${code}
\n`; + } + + return `
${code}
\n`; + }, + }); - markedOptions.mangle ??= false; // See https://github.com/TypeStrong/typedoc/issues/1395 + const loader = this.application.options.getValue("markdownItLoader"); + loader(this.parser); - return markedOptions; + // Add anchor links for headings in readme, and add them to the "On this page" section + this.parser.renderer.rules["heading_open"] = (tokens, idx) => { + const token = tokens[idx]; + const content = getTokenTextContent(tokens[idx + 1]); + const level = token.markup.length; + + const slug = this.getSlugger().slug(content); + this.lastHeaderSlug = slug; + + // Prefix the slug with an extra `md:` to prevent conflicts with TypeDoc's anchors. + this.page!.pageHeadings.push({ + link: `#md:${slug}`, + text: content, + level, + }); + + return `<${token.tag} class="tsd-anchor-link">`; + }; + this.parser.renderer.rules["heading_close"] = (tokens, idx) => { + return `${renderElement(anchorIcon(this.renderContext, `md:${this.lastHeaderSlug}`))}`; + }; + + // Rewrite anchor links inline in a readme file to links targeting the `md:` prefixed anchors + // that TypeDoc creates. + this.parser.renderer.rules["link_open"] = (tokens, idx, options, _env, self) => { + const token = tokens[idx]; + const href = token.attrGet("href")?.replace(/^#(?:md:)?(.+)/, "#md:$1"); + if (href) { + token.attrSet("href", href); + } + return self.renderToken(tokens, idx, options); + }; } /** @@ -212,29 +275,13 @@ output file : * @param event */ onParseMarkdown(event: MarkdownEvent) { - event.parsedText = Marked.marked(event.parsedText); + event.parsedText = this.parser!.render(event.parsedText); } } -// Basically a copy/paste of Marked's code, with the addition of the button -// https://github.com/markedjs/marked/blob/v4.3.0/src/Renderer.js#L15-L39 -function renderCode(this: Marked.marked.Renderer, code: string, infostring: string | undefined, escaped: boolean) { - const lang = (infostring || "").match(/\S*/)![0]; - if (this.options.highlight) { - const out = this.options.highlight(code, lang); - if (out != null && out !== code) { - escaped = true; - code = out; - } +function getTokenTextContent(token: markdown.Token): string { + if (token.children) { + return token.children.map(getTokenTextContent).join(""); } - - code = code.replace(/\n$/, "") + "\n"; - - if (!lang) { - return `
${escaped ? code : escapeHtml(code)}
\n`; - } - - return `
${
-        escaped ? code : escapeHtml(code)
-    }
\n`; + return token.content; } diff --git a/src/lib/output/themes/default/DefaultTheme.tsx b/src/lib/output/themes/default/DefaultTheme.tsx index 972fa19a5..553936c8a 100644 --- a/src/lib/output/themes/default/DefaultTheme.tsx +++ b/src/lib/output/themes/default/DefaultTheme.tsx @@ -1,17 +1,18 @@ import { Theme } from "../../theme"; import type { Renderer } from "../../renderer"; import { - Reflection, ReflectionKind, ProjectReflection, - ContainerReflection, + type ContainerReflection, DeclarationReflection, + type Reflection, SignatureReflection, ReflectionCategory, ReflectionGroup, TypeParameterReflection, + type DocumentReflection, } from "../../../models"; -import { RenderTemplate, UrlMapping } from "../../models/UrlMapping"; +import { type RenderTemplate, UrlMapping } from "../../models/UrlMapping"; import type { PageEvent } from "../../events"; import type { MarkedPlugin } from "../../plugins"; import { DefaultThemeRenderContext } from "./DefaultThemeRenderContext"; @@ -49,6 +50,43 @@ export interface NavigationElement { children?: NavigationElement[]; } +/** + * Responsible for getting a unique anchor for elements within a page. + */ +export class Slugger { + private seen = new Map(); + + private serialize(value: string) { + // Extracted from marked@4.3.0 + return ( + value + .toLowerCase() + .trim() + // remove html tags + .replace(/<[!/a-z].*?>/gi, "") + // remove unwanted chars + .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, "") + .replace(/\s/g, "-") + ); + } + + slug(value: string) { + const originalSlug = this.serialize(value); + let slug = originalSlug; + let count = 0; + if (this.seen.has(slug)) { + count = this.seen.get(originalSlug)!; + do { + count++; + slug = `${originalSlug}-${count}`; + } while (this.seen.has(slug)); + } + this.seen.set(originalSlug, count); + this.seen.set(slug, 0); + return slug; + } +} + /** * Default theme implementation of TypeDoc. If a theme does not provide a custom * {@link Theme} implementation, this theme class will be used. @@ -76,6 +114,9 @@ export class DefaultTheme extends Theme { return new DefaultThemeRenderContext(this, pageEvent, this.application.options); } + documentTemplate = (pageEvent: PageEvent) => { + return this.getRenderContext(pageEvent).documentTemplate(pageEvent); + }; reflectionTemplate = (pageEvent: PageEvent) => { return this.getRenderContext(pageEvent).reflectionTemplate(pageEvent); }; @@ -89,7 +130,7 @@ export class DefaultTheme extends Theme { return this.getRenderContext(pageEvent).defaultLayout(template, pageEvent); }; - getReflectionClasses(reflection: DeclarationReflection) { + getReflectionClasses(reflection: DeclarationReflection | DocumentReflection) { const filters = this.application.options.getValue("visibilityFilters") as Record; return getReflectionClasses(reflection, filters); } @@ -133,6 +174,11 @@ export class DefaultTheme extends Theme { directory: "variables", template: this.reflectionTemplate, }, + { + kind: [ReflectionKind.Document], + directory: "documents", + template: this.documentTemplate, + }, ]; static URL_PREFIX = /^(http|ftp)s?:\/\//; @@ -156,8 +202,9 @@ export class DefaultTheme extends Theme { */ getUrls(project: ProjectReflection): UrlMapping[] { const urls: UrlMapping[] = []; + this.sluggers.set(project, new Slugger()); - if (false == hasReadme(this.application.options.getValue("readme"))) { + if (!hasReadme(this.application.options.getValue("readme"))) { project.url = "index.html"; urls.push(new UrlMapping("index.html", project, this.reflectionTemplate)); } else if (project.children?.every((child) => child.kindOf(ReflectionKind.Module))) { @@ -175,11 +222,7 @@ export class DefaultTheme extends Theme { urls.push(new UrlMapping("hierarchy.html", project, this.hierarchyTemplate)); } - project.children?.forEach((child: Reflection) => { - if (child instanceof DeclarationReflection) { - this.buildUrls(child, urls); - } - }); + project.childrenIncludingDocuments?.forEach((child) => this.buildUrls(child, urls)); return urls; } @@ -208,7 +251,7 @@ export class DefaultTheme extends Theme { * @param reflection The reflection whose mapping should be resolved. * @returns The found mapping or undefined if no mapping could be found. */ - private getMapping(reflection: DeclarationReflection): TemplateMapping | undefined { + private getMapping(reflection: DeclarationReflection | DocumentReflection): TemplateMapping | undefined { return this.mappings.find((mapping) => reflection.kindOf(mapping.kind)); } @@ -219,19 +262,20 @@ export class DefaultTheme extends Theme { * @param urls The array the url should be appended to. * @returns The altered urls array. */ - buildUrls(reflection: DeclarationReflection, urls: UrlMapping[]): UrlMapping[] { + buildUrls(reflection: DeclarationReflection | DocumentReflection, urls: UrlMapping[]): UrlMapping[] { const mapping = this.getMapping(reflection); if (mapping) { if (!reflection.url || !DefaultTheme.URL_PREFIX.test(reflection.url)) { const url = [mapping.directory, DefaultTheme.getUrl(reflection) + ".html"].join("/"); urls.push(new UrlMapping(url, reflection, mapping.template)); + this.sluggers.set(reflection, new Slugger()); reflection.url = url; reflection.hasOwnDocument = true; } reflection.traverse((child) => { - if (child instanceof DeclarationReflection) { + if (child.isDeclaration() || child.isDocument()) { this.buildUrls(child, urls); } else { DefaultTheme.applyAnchorUrl(child, reflection); @@ -271,16 +315,10 @@ export class DefaultTheme extends Theme { const opts = this.application.options.getValue("navigation"); const leaves = this.application.options.getValue("navigationLeaves"); - if (opts.fullTree) { - this.application.logger.warn( - `The navigation.fullTree option no longer has any affect and will be removed in v0.26`, - ); - } - return getNavigationElements(project) || []; function toNavigation( - element: ReflectionCategory | ReflectionGroup | DeclarationReflection, + element: ReflectionCategory | ReflectionGroup | DeclarationReflection | DocumentReflection, ): NavigationElement { if (element instanceof ReflectionCategory || element instanceof ReflectionGroup) { return { @@ -299,7 +337,12 @@ export class DefaultTheme extends Theme { } function getNavigationElements( - parent: ReflectionCategory | ReflectionGroup | DeclarationReflection | ProjectReflection, + parent: + | ReflectionCategory + | ReflectionGroup + | DeclarationReflection + | ProjectReflection + | DocumentReflection, ): undefined | NavigationElement[] { if (parent instanceof ReflectionCategory) { return parent.children.map(toNavigation); @@ -316,10 +359,20 @@ export class DefaultTheme extends Theme { return; } - if (!parent.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)) { + if (!parent.kindOf(ReflectionKind.MayContainDocuments)) { return; } + if (parent.isDocument()) { + return parent.children?.map(toNavigation); + } + + if (!parent.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)) { + // Tricky: Non-module children don't show up in the navigation pane, + // but any documents added by them should. + return parent.documents?.map(toNavigation); + } + if (parent.categories && shouldShowCategories(parent, opts)) { return parent.categories.map(toNavigation); } @@ -328,18 +381,14 @@ export class DefaultTheme extends Theme { return parent.groups.map(toNavigation); } - if ( - opts.includeFolders && - parent.children?.every((child) => child.kindOf(ReflectionKind.Module)) && - parent.children.some((child) => child.name.includes("/")) - ) { - return deriveModuleFolders(parent.children); + if (opts.includeFolders && parent.childrenIncludingDocuments?.some((child) => child.name.includes("/"))) { + return deriveModuleFolders(parent.childrenIncludingDocuments); } - return parent.children?.map(toNavigation); + return parent.childrenIncludingDocuments?.map(toNavigation); } - function deriveModuleFolders(children: DeclarationReflection[]) { + function deriveModuleFolders(children: Array) { const result: NavigationElement[] = []; const resolveOrCreateParents = ( @@ -395,6 +444,16 @@ export class DefaultTheme extends Theme { } } + private sluggers = new Map(); + + getSlugger(reflection: Reflection): Slugger { + if (this.sluggers.has(reflection)) { + return this.sluggers.get(reflection)!; + } + // A slugger should always be defined at least for the project + return this.getSlugger(reflection.parent!); + } + /** * Generate an anchor url for the given reflection and all of its children. * @@ -413,7 +472,7 @@ export class DefaultTheme extends Theme { if (!reflection.url || !DefaultTheme.URL_PREFIX.test(reflection.url)) { const anchor = DefaultTheme.getUrl(reflection, container, "."); - reflection.url = container.url + "#" + anchor; + reflection.url = container.url! + "#" + anchor; reflection.anchor = anchor; reflection.hasOwnDocument = false; } @@ -429,14 +488,17 @@ function hasReadme(readme: string) { return !readme.endsWith("none"); } -function getReflectionClasses(reflection: DeclarationReflection, filters: Record) { +function getReflectionClasses( + reflection: DeclarationReflection | DocumentReflection, + filters: Record, +) { const classes: string[] = []; // Filter classes should match up with the settings function in // partials/navigation.tsx. for (const key of Object.keys(filters)) { if (key === "inherited") { - if (reflection.inheritedFrom) { + if (reflection.flags.isInherited) { classes.push("tsd-is-inherited"); } } else if (key === "protected") { diff --git a/src/lib/output/themes/default/DefaultThemeRenderContext.ts b/src/lib/output/themes/default/DefaultThemeRenderContext.ts index 0e663f377..2e0b423ff 100644 --- a/src/lib/output/themes/default/DefaultThemeRenderContext.ts +++ b/src/lib/output/themes/default/DefaultThemeRenderContext.ts @@ -1,15 +1,18 @@ -import type { PageEvent, RendererHooks } from "../.."; -import { - Comment, +import type { PageEvent, Renderer } from "../.."; +import type { + Internationalization, + TranslationProxy, +} from "../../../internationalization/internationalization"; +import type { + DocumentReflection, CommentDisplayPart, DeclarationReflection, Reflection, } from "../../../models"; -import { JSX, NeverIfInternal, Options } from "../../../utils"; +import { type NeverIfInternal, type Options } from "../../../utils"; import type { DefaultTheme } from "./DefaultTheme"; import { defaultLayout } from "./layouts/default"; import { index } from "./partials"; -import { analytics } from "./partials/analytics"; import { breadcrumb } from "./partials/breadcrumb"; import { commentSummary, @@ -19,7 +22,7 @@ import { import { footer } from "./partials/footer"; import { header } from "./partials/header"; import { hierarchy } from "./partials/hierarchy"; -import { buildRefIcons, icons } from "./partials/icon"; +import { buildRefIcons, type icons } from "./partials/icon"; import { member } from "./partials/member"; import { memberDeclaration } from "./partials/member.declaration"; import { memberGetterSetter } from "./partials/member.getterSetter"; @@ -29,7 +32,6 @@ import { memberSignatureTitle } from "./partials/member.signature.title"; import { memberSignatures } from "./partials/member.signatures"; import { memberSources } from "./partials/member.sources"; import { members } from "./partials/members"; -import { membersGroup } from "./partials/members.group"; import { sidebar, pageSidebar, @@ -45,6 +47,7 @@ import { type } from "./partials/type"; import { typeAndParent } from "./partials/typeAndParent"; import { typeParameters } from "./partials/typeParameters"; import { indexTemplate } from "./templates"; +import { documentTemplate } from "./templates/document"; import { hierarchyTemplate } from "./templates/hierarchy"; import { reflectionTemplate } from "./templates/reflection"; @@ -55,21 +58,18 @@ function bind(fn: (f: F, ...a: L) => R, first: F) { export class DefaultThemeRenderContext { private _refIcons: typeof icons; options: Options; + internationalization: Internationalization; + i18n: TranslationProxy; constructor( - private theme: DefaultTheme, + readonly theme: DefaultTheme, public page: PageEvent, options: Options, ) { this.options = options; - this._refIcons = buildRefIcons(icons, this); - } - - /** - * @deprecated Will be removed in 0.26, no longer required. - */ - iconsCache(): JSX.Element { - return JSX.createElement(JSX.Fragment, null); + this.internationalization = theme.application.internationalization; + this.i18n = this.internationalization.proxy; + this._refIcons = buildRefIcons(theme.icons, this); } /** @@ -82,8 +82,9 @@ export class DefaultThemeRenderContext { return this._refIcons; } - hook = (name: keyof RendererHooks) => - this.theme.owner.hooks.emit(name, this); + hook: Renderer["hooks"]["emit"] = (...params) => { + return this.theme.owner.hooks.emit(...params); + }; /** Avoid this in favor of urlTo if possible */ relativeURL = (url: string, cacheBust = false) => { @@ -101,20 +102,15 @@ export class DefaultThemeRenderContext { markdown = ( md: readonly CommentDisplayPart[] | NeverIfInternal, ) => { - if (md instanceof Array) { - return this.theme.markedPlugin.parseMarkdown( - Comment.displayPartsToMarkdown(md, this.urlTo), - this.page, - ); - } - return md ? this.theme.markedPlugin.parseMarkdown(md, this.page) : ""; + return this.theme.markedPlugin.parseMarkdown(md || "", this.page, this); }; getNavigation = () => this.theme.getNavigation(this.page.project); - getReflectionClasses = (refl: DeclarationReflection) => + getReflectionClasses = (refl: DeclarationReflection | DocumentReflection) => this.theme.getReflectionClasses(refl); + documentTemplate = bind(documentTemplate, this); reflectionTemplate = bind(reflectionTemplate, this); indexTemplate = bind(indexTemplate, this); hierarchyTemplate = bind(hierarchyTemplate, this); @@ -130,7 +126,6 @@ export class DefaultThemeRenderContext { */ reflectionPreview = bind(reflectionPreview, this); - analytics = bind(analytics, this); breadcrumb = bind(breadcrumb, this); commentSummary = bind(commentSummary, this); commentTags = bind(commentTags, this); @@ -148,7 +143,8 @@ export class DefaultThemeRenderContext { memberSignatures = bind(memberSignatures, this); memberSources = bind(memberSources, this); members = bind(members, this); - membersGroup = bind(membersGroup, this); + /** @deprecated Since 0.26.3 members does group/category flattening internally */ + membersGroup?: Function; sidebar = bind(sidebar, this); pageSidebar = bind(pageSidebar, this); sidebarLinks = bind(sidebarLinks, this); diff --git a/src/lib/output/themes/default/assets/bootstrap.ts b/src/lib/output/themes/default/assets/bootstrap.ts index 5184eb086..a7168939d 100644 --- a/src/lib/output/themes/default/assets/bootstrap.ts +++ b/src/lib/output/themes/default/assets/bootstrap.ts @@ -7,7 +7,7 @@ import { initTheme } from "./typedoc/Theme"; import { initNav } from "./typedoc/Navigation"; registerComponent(Toggle, "a[data-toggle]"); -registerComponent(Accordion, ".tsd-index-accordion"); +registerComponent(Accordion, ".tsd-accordion"); registerComponent(Filter, ".tsd-filter-item input[type=checkbox]"); const themeChoice = document.getElementById("tsd-theme"); diff --git a/src/lib/output/themes/default/assets/typedoc/Application.ts b/src/lib/output/themes/default/assets/typedoc/Application.ts index 0406e1d27..66fb784f8 100644 --- a/src/lib/output/themes/default/assets/typedoc/Application.ts +++ b/src/lib/output/themes/default/assets/typedoc/Application.ts @@ -1,5 +1,15 @@ import type { IComponentOptions } from "./Component"; +declare global { + interface Window { + translations: { + copy: string; + copied: string; + normally_hidden: string; + }; + } +} + /** * Component definition. */ @@ -68,7 +78,6 @@ export class Application { public showPage() { if (!document.body.style.display) return; - console.log("Show page"); document.body.style.removeProperty("display"); this.ensureFocusedElementVisible(); this.updateIndexVisibility(); @@ -79,7 +88,6 @@ export class Application { // Because we hid the entire page until the navigation loaded or we hit a timeout, // we have to manually resolve the url hash here. if (location.hash) { - console.log("Scorlling"); const reflAnchor = document.getElementById( location.hash.substring(1), ); @@ -161,6 +169,16 @@ export class Application { return; } + // Ensure the group this reflection is contained within is visible. + const wasHidden = reflContainer.offsetParent == null; + let sectionContainer = reflContainer; + while (sectionContainer !== document.body) { + if (sectionContainer instanceof HTMLDetailsElement) { + sectionContainer.open = true; + } + sectionContainer = sectionContainer.parentElement!; + } + if (reflContainer.offsetParent == null) { this.alwaysVisibleMember = reflContainer; @@ -168,11 +186,14 @@ export class Application { const warning = document.createElement("p"); warning.classList.add("warning"); - warning.textContent = - "This member is normally hidden due to your filter settings."; + warning.textContent = window.translations.normally_hidden; reflContainer.prepend(warning); } + + if (wasHidden) { + reflAnchor.scrollIntoView(); + } } private listenForCodeCopies() { @@ -184,13 +205,13 @@ export class Application { button.previousElementSibling.innerText.trim(), ); } - button.textContent = "Copied!"; + button.textContent = window.translations.copied; button.classList.add("visible"); clearTimeout(timeout); timeout = setTimeout(() => { button.classList.remove("visible"); timeout = setTimeout(() => { - button.textContent = "Copy"; + button.textContent = window.translations.copy; }, 100); }, 1000); }); diff --git a/src/lib/output/themes/default/assets/typedoc/Navigation.ts b/src/lib/output/themes/default/assets/typedoc/Navigation.ts index 87eae39df..0bd320e9b 100644 --- a/src/lib/output/themes/default/assets/typedoc/Navigation.ts +++ b/src/lib/output/themes/default/assets/typedoc/Navigation.ts @@ -34,7 +34,8 @@ async function buildNav() { .pipeThrough(new DecompressionStream("gzip")); const nav: NavigationElement[] = await new Response(json).json(); - BASE_URL = container.dataset.base + "/"; + BASE_URL = container.dataset.base!; + if (!BASE_URL.endsWith("/")) BASE_URL += "/"; container.innerHTML = ""; for (const el of nav) { buildNavElement(el, container, []); @@ -56,12 +57,12 @@ function buildNavElement( const fullPath = [...path, el.text]; const details = li.appendChild(document.createElement("details")); details.className = el.class - ? `${el.class} tsd-index-accordion` - : "tsd-index-accordion"; - details.dataset.key = fullPath.join("$"); + ? `${el.class} tsd-accordion` + : "tsd-accordion"; const summary = details.appendChild(document.createElement("summary")); summary.className = "tsd-accordion-summary"; + summary.dataset.key = fullPath.join("$"); // Would be nice to not hardcode this here, if someone overwrites the chevronDown icon with an // then this won't work... going to wait to worry about that until it actually breaks some custom theme. // Also very annoying that we can't encode the svg in the cache, since that gets duplicated here... diff --git a/src/lib/output/themes/default/assets/typedoc/components/Search.ts b/src/lib/output/themes/default/assets/typedoc/components/Search.ts index 9e7f282fb..7e1d553ac 100644 --- a/src/lib/output/themes/default/assets/typedoc/components/Search.ts +++ b/src/lib/output/themes/default/assets/typedoc/components/Search.ts @@ -80,20 +80,11 @@ export function initSearch() { ); } - let resultClicked = false; - results.addEventListener("mousedown", () => (resultClicked = true)); results.addEventListener("mouseup", () => { - resultClicked = false; - searchEl.classList.remove("has-focus"); + hideSearch(searchEl); }); field.addEventListener("focus", () => searchEl.classList.add("has-focus")); - field.addEventListener("blur", () => { - if (!resultClicked) { - resultClicked = false; - searchEl.classList.remove("has-focus"); - } - }); bindEvents(searchEl, results, field, state); } @@ -111,37 +102,48 @@ function bindEvents( }, 200), ); - let preventPress = false; + // Narrator is a pain. It completely eats the up/down arrow key events, so we can't + // rely on detecting the input blurring to hide the focus. We have to instead check + // for a focus event on an item outside of the search field/results. field.addEventListener("keydown", (e) => { - preventPress = true; if (e.key == "Enter") { - gotoCurrentResult(results, field); - } else if (e.key == "Escape") { - field.blur(); + gotoCurrentResult(results, searchEl); } else if (e.key == "ArrowUp") { - setCurrentResult(results, -1); + setCurrentResult(results, field, -1); + e.preventDefault(); } else if (e.key === "ArrowDown") { - setCurrentResult(results, 1); - } else { - preventPress = false; + setCurrentResult(results, field, 1); + e.preventDefault(); } }); - field.addEventListener("keypress", (e) => { - if (preventPress) e.preventDefault(); - }); /** * Start searching by pressing slash. */ - document.body.addEventListener("keydown", (e) => { + document.body.addEventListener("keypress", (e) => { if (e.altKey || e.ctrlKey || e.metaKey) return; if (!field.matches(":focus") && e.key === "/") { - field.focus(); e.preventDefault(); + field.focus(); + } + }); + + document.body.addEventListener("keyup", (e) => { + if ( + searchEl.classList.contains("has-focus") && + (e.key === "Escape" || + (!results.matches(":focus-within") && !field.matches(":focus"))) + ) { + field.blur(); + hideSearch(searchEl); } }); } +function hideSearch(searchEl: HTMLElement) { + searchEl.classList.remove("has-focus"); +} + function updateResults( searchEl: HTMLElement, results: HTMLElement, @@ -223,6 +225,11 @@ function updateResults( anchor.innerHTML = icon + name; item.append(anchor); + anchor.addEventListener("focus", () => { + results.querySelector(".current")?.classList.remove("current"); + item.classList.add("current"); + }); + results.appendChild(item); } } @@ -230,7 +237,11 @@ function updateResults( /** * Move the highlight within the result set. */ -function setCurrentResult(results: HTMLElement, dir: number) { +function setCurrentResult( + results: HTMLElement, + field: HTMLInputElement, + dir: number, +) { let current = results.querySelector(".current"); if (!current) { current = results.querySelector( @@ -256,6 +267,9 @@ function setCurrentResult(results: HTMLElement, dir: number) { if (rel) { current.classList.remove("current"); rel.classList.add("current"); + } else if (dir === -1) { + current.classList.remove("current"); + field.focus(); } } } @@ -263,7 +277,7 @@ function setCurrentResult(results: HTMLElement, dir: number) { /** * Navigate to the highlighted result. */ -function gotoCurrentResult(results: HTMLElement, field: HTMLInputElement) { +function gotoCurrentResult(results: HTMLElement, searchEl: HTMLElement) { let current = results.querySelector(".current"); if (!current) { @@ -275,7 +289,7 @@ function gotoCurrentResult(results: HTMLElement, field: HTMLInputElement) { if (link) { window.location.href = link.href; } - field.blur(); + hideSearch(searchEl); } } diff --git a/src/lib/output/themes/default/layouts/default.tsx b/src/lib/output/themes/default/layouts/default.tsx index c513e99ec..e06c36bf7 100644 --- a/src/lib/output/themes/default/layouts/default.tsx +++ b/src/lib/output/themes/default/layouts/default.tsx @@ -10,10 +10,10 @@ export const defaultLayout = ( template: RenderTemplate>, props: PageEvent, ) => ( - + - - {context.hook("head.begin")} + + {context.hook("head.begin", context)} {props.model.isProject() @@ -32,10 +32,10 @@ export const defaultLayout = ( <script async src={context.relativeURL("assets/icons.js", true)} id="tsd-icons-script"></script> <script async src={context.relativeURL("assets/search.js", true)} id="tsd-search-script"></script> <script async src={context.relativeURL("assets/navigation.js", true)} id="tsd-nav-script"></script> - {context.hook("head.end")} + {context.hook("head.end", context)} </head> <body> - {context.hook("body.begin")} + {context.hook("body.begin", context)} <script> <Raw html='document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";' /> {/* Hide the entire page for up to 0.5 seconds so that if navigating between pages on a fast */} @@ -50,21 +50,21 @@ export const defaultLayout = ( <div class="container container-main"> <div class="col-content"> - {context.hook("content.begin")} + {context.hook("content.begin", context)} {context.header(props)} {template(props)} - {context.hook("content.end")} + {context.hook("content.end", context)} </div> <div class="col-sidebar"> <div class="page-menu"> - {context.hook("pageSidebar.begin")} + {context.hook("pageSidebar.begin", context)} {context.pageSidebar(props)} - {context.hook("pageSidebar.end")} + {context.hook("pageSidebar.end", context)} </div> <div class="site-menu"> - {context.hook("sidebar.begin")} + {context.hook("sidebar.begin", context)} {context.sidebar(props)} - {context.hook("sidebar.end")} + {context.hook("sidebar.end", context)} </div> </div> </div> @@ -73,8 +73,7 @@ export const defaultLayout = ( <div class="overlay"></div> - {context.analytics()} - {context.hook("body.end")} + {context.hook("body.end", context)} </body> </html> ); diff --git a/src/lib/output/themes/default/partials/analytics.tsx b/src/lib/output/themes/default/partials/analytics.tsx deleted file mode 100644 index afcf8fbbd..000000000 --- a/src/lib/output/themes/default/partials/analytics.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; -import { JSX } from "../../../../utils"; - -export function analytics(context: DefaultThemeRenderContext) { - const gaID = context.options.getValue("gaID"); - if (!gaID) return; - - const script = ` -window.dataLayer = window.dataLayer || []; -function gtag(){dataLayer.push(arguments);} -gtag('js', new Date()); -gtag('config', '${gaID}'); -`.trim(); - - return ( - <> - <script async src={"https://www.googletagmanager.com/gtag/js?id=" + gaID}></script> - <script> - <JSX.Raw html={script} /> - </script> - </> - ); -} diff --git a/src/lib/output/themes/default/partials/anchor-icon.tsx b/src/lib/output/themes/default/partials/anchor-icon.tsx index 0b376ce52..72a546925 100644 --- a/src/lib/output/themes/default/partials/anchor-icon.tsx +++ b/src/lib/output/themes/default/partials/anchor-icon.tsx @@ -5,7 +5,7 @@ export function anchorIcon(context: DefaultThemeRenderContext, anchor: string | if (!anchor) return <></>; return ( - <a href={`#${anchor}`} aria-label="Permalink" class="tsd-anchor-icon"> + <a href={`#${anchor}`} aria-label={context.i18n.theme_permalink()} class="tsd-anchor-icon"> {context.icons.anchor()} </a> ); diff --git a/src/lib/output/themes/default/partials/comment.tsx b/src/lib/output/themes/default/partials/comment.tsx index 768b70469..81259c049 100644 --- a/src/lib/output/themes/default/partials/comment.tsx +++ b/src/lib/output/themes/default/partials/comment.tsx @@ -1,7 +1,8 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX, Raw } from "../../../../utils"; -import { Reflection, ReflectionKind } from "../../../../models"; -import { camelToTitleCase } from "../../lib"; +import { type Reflection, ReflectionKind } from "../../../../models"; +import { anchorIcon } from "./anchor-icon"; +import { join } from "../../lib"; // Note: Comment modifiers are handled in `renderFlags` @@ -15,50 +16,55 @@ export function commentSummary({ markdown }: DefaultThemeRenderContext, props: R ); } -export function commentTags({ markdown }: DefaultThemeRenderContext, props: Reflection) { +export function commentTags(context: DefaultThemeRenderContext, props: Reflection) { if (!props.comment) return; + const beforeTags = context.hook("comment.beforeTags", context, props.comment, props); + const afterTags = context.hook("comment.afterTags", context, props.comment, props); + const tags = props.kindOf(ReflectionKind.SomeSignature) - ? props.comment.blockTags.filter((tag) => tag.tag !== "@returns") - : props.comment.blockTags; + ? props.comment.blockTags.filter((tag) => tag.tag !== "@returns" && !tag.skipRendering) + : props.comment.blockTags.filter((tag) => !tag.skipRendering); return ( - <div class="tsd-comment tsd-typography"> - {tags.map((item) => { - const name = item.name - ? `${camelToTitleCase(item.tag.substring(1))}: ${item.name}` - : camelToTitleCase(item.tag.substring(1)); + <> + {beforeTags} + <div class="tsd-comment tsd-typography"> + {tags.map((item) => { + const name = item.name + ? `${context.internationalization.translateTagName(item.tag)}: ${item.name}` + : context.internationalization.translateTagName(item.tag); - return ( - <> - <h4>{name}</h4> - <Raw html={markdown(item.content)} /> - </> - ); - })} - </div> + const anchor = props.getUniqueAliasInPage(name); + + return ( + <> + <h4 class="tsd-anchor-link"> + <a id={anchor} class="tsd-anchor"></a> + {name} + {anchorIcon(context, anchor)} + </h4> + <Raw html={context.markdown(item.content)} /> + </> + ); + })} + </div> + {afterTags} + </> ); } const flagsNotRendered: `@${string}`[] = ["@showCategories", "@showGroups", "@hideCategories", "@hideGroups"]; -export function reflectionFlags(_context: DefaultThemeRenderContext, props: Reflection) { - const allFlags = [...props.flags]; +export function reflectionFlags(context: DefaultThemeRenderContext, props: Reflection) { + const allFlags = props.flags.getFlagStrings(context.internationalization); if (props.comment) { for (const tag of props.comment.modifierTags) { if (!flagsNotRendered.includes(tag)) { - allFlags.push(camelToTitleCase(tag.substring(1))); + allFlags.push(context.internationalization.translateTagName(tag)); } } } - return ( - <> - {allFlags.map((item) => ( - <> - <code class={"tsd-tag ts-flag" + item}>{item}</code>{" "} - </> - ))} - </> - ); + return join(" ", allFlags, (item) => <code class="tsd-tag">{item}</code>); } diff --git a/src/lib/output/themes/default/partials/footer.tsx b/src/lib/output/themes/default/partials/footer.tsx index 0bca03947..137c80c6c 100644 --- a/src/lib/output/themes/default/partials/footer.tsx +++ b/src/lib/output/themes/default/partials/footer.tsx @@ -3,18 +3,49 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; export function footer(context: DefaultThemeRenderContext) { const hideGenerator = context.options.getValue("hideGenerator"); - return ( - <footer> - {context.hook("footer.begin")} - {hideGenerator || ( + let generatorDisplay = <></>; + if (!hideGenerator) { + const message = context.i18n.theme_generated_using_typedoc(); + + // Only handles one occurrence, but that's all I expect... + const index = message.indexOf("TypeDoc"); + if (index == -1) { + generatorDisplay = <p class="tsd-generator">{message}</p>; + } else { + const pre = message.substring(0, index); + const post = message.substring(index + "TypeDoc".length); + generatorDisplay = ( <p class="tsd-generator"> - {"Generated using "} + {pre} <a href="https://typedoc.org/" target="_blank"> TypeDoc </a> + {post} + </p> + ); + } + } + + const customFooterHtml = context.options.getValue("customFooterHtml"); + let customFooterDisplay = <></>; + if (customFooterHtml) { + if (context.options.getValue("customFooterHtmlDisableWrapper")) { + customFooterDisplay = <JSX.Raw html={customFooterHtml} />; + } else { + customFooterDisplay = ( + <p> + <JSX.Raw html={customFooterHtml} /> </p> - )} - {context.hook("footer.end")} + ); + } + } + + return ( + <footer> + {context.hook("footer.begin", context)} + {generatorDisplay} + {customFooterDisplay} + {context.hook("footer.end", context)} </footer> ); } diff --git a/src/lib/output/themes/default/partials/header.tsx b/src/lib/output/themes/default/partials/header.tsx index 9e434b68c..87e3c5108 100644 --- a/src/lib/output/themes/default/partials/header.tsx +++ b/src/lib/output/themes/default/partials/header.tsx @@ -2,25 +2,28 @@ import { classNames, getDisplayName, hasTypeParameters, join } from "../../lib"; import { JSX } from "../../../../utils"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import type { PageEvent } from "../../../events"; -import { Reflection, ReflectionKind } from "../../../../models"; +import { type Reflection, ReflectionKind } from "../../../../models"; export const header = (context: DefaultThemeRenderContext, props: PageEvent<Reflection>) => { const HeadingLevel = props.model.isProject() ? "h2" : "h1"; return ( <div class="tsd-page-title"> {!!props.model.parent && <ul class="tsd-breadcrumb">{context.breadcrumb(props.model)}</ul>} - <HeadingLevel class={classNames({ deprecated: props.model.isDeprecated() })}> - {props.model.kind !== ReflectionKind.Project && `${ReflectionKind.singularString(props.model.kind)} `} - {getDisplayName(props.model)} - {hasTypeParameters(props.model) && ( - <> - {"<"} - {join(", ", props.model.typeParameters, (item) => item.name)} - {">"} - </> - )} - {context.reflectionFlags(props.model)} - </HeadingLevel> + {!props.model.isDocument() && ( + <HeadingLevel class={classNames({ deprecated: props.model.isDeprecated() })}> + {props.model.kind !== ReflectionKind.Project && + `${context.internationalization.kindSingularString(props.model.kind)} `} + {getDisplayName(props.model)} + {hasTypeParameters(props.model) && ( + <> + {"<"} + {join(", ", props.model.typeParameters, (item) => item.name)} + {">"} + </> + )} + {context.reflectionFlags(props.model)} + </HeadingLevel> + )} </div> ); }; diff --git a/src/lib/output/themes/default/partials/hierarchy.tsx b/src/lib/output/themes/default/partials/hierarchy.tsx index fa979eeb3..5d13d14fa 100644 --- a/src/lib/output/themes/default/partials/hierarchy.tsx +++ b/src/lib/output/themes/default/partials/hierarchy.tsx @@ -22,8 +22,8 @@ export function hierarchy(context: DefaultThemeRenderContext, props: Declaration <> {" "} ( - <a class="link" href={context.relativeURL("hierarchy.html") + "#" + context.page.model.getFullName()}> - view full + <a href={context.relativeURL("hierarchy.html") + "#" + context.page.model.getFullName()}> + {context.i18n.theme_hierarchy_view_full()} </a> ) </> @@ -33,7 +33,10 @@ export function hierarchy(context: DefaultThemeRenderContext, props: Declaration return ( <section class="tsd-panel tsd-hierarchy"> - <h4>Hierarchy{fullLink}</h4> + <h4> + {context.i18n.theme_hierarchy()} + {fullLink} + </h4> {hierarchyList(context, props)} </section> ); diff --git a/src/lib/output/themes/default/partials/icon.tsx b/src/lib/output/themes/default/partials/icon.tsx index c899cf5cb..b50eddeeb 100644 --- a/src/lib/output/themes/default/partials/icon.tsx +++ b/src/lib/output/themes/default/partials/icon.tsx @@ -184,6 +184,16 @@ export const icons: Record< />, "var(--color-ts-variable)", ), + [ReflectionKind.Document]: () => + kindIcon( + <g stroke="var(--color-text)" fill="var(--color-icon-background)"> + <polygon points="6,5 6,19 18,19, 18,9 15,5" /> + <line x1="9" y1="9" x2="14" y2="9" /> + <line x1="9" y1="12" x2="15" y2="12" /> + <line x1="9" y1="15" x2="15" y2="15" /> + </g>, + "var(--color-document)", + ), chevronDown: () => ( <svg width="20" height="20" viewBox="0 0 24 24" fill="none"> <path diff --git a/src/lib/output/themes/default/partials/index.tsx b/src/lib/output/themes/default/partials/index.tsx index a68058234..d35d0ba28 100644 --- a/src/lib/output/themes/default/partials/index.tsx +++ b/src/lib/output/themes/default/partials/index.tsx @@ -57,10 +57,10 @@ export function index(context: DefaultThemeRenderContext, props: ContainerReflec ) ) { content = ( - <details class="tsd-index-content tsd-index-accordion" open={true}> + <details class="tsd-index-content tsd-accordion" open={true}> <summary class="tsd-accordion-summary tsd-index-summary"> <h5 class="tsd-index-heading uppercase" role="button" aria-expanded="false" tabIndex={0}> - {context.icons.chevronSmall()} Index + {context.icons.chevronSmall()} {context.i18n.theme_index()} </h5> </summary> <div class="tsd-accordion-details">{content}</div> @@ -69,7 +69,7 @@ export function index(context: DefaultThemeRenderContext, props: ContainerReflec } else { content = ( <> - <h3 class="tsd-index-heading uppercase">Index</h3> + <h3 class="tsd-index-heading uppercase">{context.i18n.theme_index()}</h3> {content} </> ); diff --git a/src/lib/output/themes/default/partials/member.declaration.tsx b/src/lib/output/themes/default/partials/member.declaration.tsx index 7ed7ce935..0dc770370 100644 --- a/src/lib/output/themes/default/partials/member.declaration.tsx +++ b/src/lib/output/themes/default/partials/member.declaration.tsx @@ -1,16 +1,30 @@ import type { DeclarationReflection, ReflectionType } from "../../../../models"; -import { JSX } from "../../../../utils"; +import { JSX, Raw } from "../../../../utils"; import { getKindClass, hasTypeParameters, renderTypeParametersSignature, wbr } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; +function renderingTypeDeclarationIsUseful(declaration: DeclarationReflection): boolean { + if (declaration.hasComment()) return true; + if (declaration.children?.some(renderingTypeDeclarationIsUseful)) return true; + if (declaration.type?.type === "reflection" && renderingTypeDeclarationIsUseful(declaration.type.declaration)) { + return true; + } + + return declaration.getAllSignatures().some((sig) => { + return sig.hasComment() || sig.parameters?.some((p) => p.hasComment()); + }); +} + export function memberDeclaration(context: DefaultThemeRenderContext, props: DeclarationReflection) { function renderTypeDeclaration(type: ReflectionType) { - return ( - <div class="tsd-type-declaration"> - <h4>Type declaration</h4> - {context.parameter(type.declaration)} - </div> - ); + if (renderingTypeDeclarationIsUseful(type.declaration)) { + return ( + <div class="tsd-type-declaration"> + <h4>{context.i18n.theme_type_declaration()}</h4> + {context.parameter(type.declaration)} + </div> + ); + } } const visitor = { reflection: renderTypeDeclaration }; @@ -44,7 +58,22 @@ export function memberDeclaration(context: DefaultThemeRenderContext, props: Dec reflection: renderTypeDeclaration, array: (arr) => arr.elementType.visit(visitor), intersection: (int) => int.types.map((t) => t.visit(visitor)), - union: (union) => union.types.map((t) => t.visit(visitor)), + union: (union) => { + if (union.elementSummaries) { + const result: JSX.Children = []; + for (let i = 0; i < union.types.length; ++i) { + result.push( + <li> + {context.type(union.types[i])} + <Raw html={context.markdown(union.elementSummaries[i])} /> + {union.types[i].visit(visitor)} + </li>, + ); + } + return <ul>{result}</ul>; + } + return union.types.map((t) => t.visit(visitor)); + }, reference: (ref) => ref.typeArguments?.map((t) => t.visit(visitor)), tuple: (ref) => ref.elements.map((t) => t.visit(visitor)), })} diff --git a/src/lib/output/themes/default/partials/member.reference.tsx b/src/lib/output/themes/default/partials/member.reference.tsx index 77ea0e710..709ec1388 100644 --- a/src/lib/output/themes/default/partials/member.reference.tsx +++ b/src/lib/output/themes/default/partials/member.reference.tsx @@ -2,24 +2,28 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX } from "../../../../utils"; import type { ReferenceReflection } from "../../../../models"; -export const memberReference = ({ urlTo }: DefaultThemeRenderContext, props: ReferenceReflection) => { +export const memberReference = ({ urlTo, i18n }: DefaultThemeRenderContext, props: ReferenceReflection) => { const referenced = props.tryGetTargetReflectionDeep(); if (!referenced) { - return <>Re-exports {props.name}</>; + return ( + <> + {i18n.theme_re_exports()} {props.name} + </> + ); } if (props.name === referenced.name) { return ( <> - Re-exports <a href={urlTo(referenced)}>{referenced.name}</a> + {i18n.theme_re_exports()} <a href={urlTo(referenced)}>{referenced.name}</a> </> ); } return ( <> - Renames and re-exports <a href={urlTo(referenced)}>{referenced.name}</a> + {i18n.theme_renames_and_re_exports()} <a href={urlTo(referenced)}>{referenced.name}</a> </> ); }; 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 e62efeb72..f979c6d29 100644 --- a/src/lib/output/themes/default/partials/member.signature.body.tsx +++ b/src/lib/output/themes/default/partials/member.signature.body.tsx @@ -1,6 +1,6 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX, Raw } from "../../../../utils"; -import { ReflectionType, SignatureReflection } from "../../../../models"; +import { ReflectionType, type SignatureReflection } from "../../../../models"; import { hasTypeParameters } from "../../lib"; export function memberSignatureBody( @@ -19,7 +19,7 @@ export function memberSignatureBody( {props.parameters && props.parameters.length > 0 && ( <div class="tsd-parameters"> - <h4 class="tsd-parameters-title">Parameters</h4> + <h4 class="tsd-parameters-title">{context.i18n.kind_plural_parameter()}</h4> <ul class="tsd-parameter-list"> {props.parameters.map((item) => ( <li> @@ -47,8 +47,7 @@ export function memberSignatureBody( {props.type && ( <> <h4 class="tsd-returns-title"> - {"Returns "} - {context.type(props.type)} + {context.i18n.theme_returns()} {context.type(props.type)} </h4> {returnsTag && <Raw html={context.markdown(returnsTag.content)} />} {props.type instanceof ReflectionType && context.parameter(props.type.declaration)} diff --git a/src/lib/output/themes/default/partials/member.signature.title.tsx b/src/lib/output/themes/default/partials/member.signature.title.tsx index 659e2f0fa..179a9acab 100644 --- a/src/lib/output/themes/default/partials/member.signature.title.tsx +++ b/src/lib/output/themes/default/partials/member.signature.title.tsx @@ -1,7 +1,7 @@ import { getKindClass, join, renderTypeParametersSignature, wbr } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX } from "../../../../utils"; -import { ParameterReflection, ReflectionKind, SignatureReflection } from "../../../../models"; +import { type ParameterReflection, ReflectionKind, type SignatureReflection } from "../../../../models"; function renderParameterWithType(context: DefaultThemeRenderContext, item: ParameterReflection) { return ( @@ -31,9 +31,12 @@ function renderParameterWithoutType(item: ParameterReflection) { export function memberSignatureTitle( context: DefaultThemeRenderContext, props: SignatureReflection, - { hideName = false, arrowStyle = false }: { hideName?: boolean; arrowStyle?: boolean } = {}, + { + hideName = false, + arrowStyle = false, + hideParamTypes = context.options.getValue("hideParameterTypesInTitle"), + }: { hideName?: boolean; arrowStyle?: boolean; hideParamTypes?: boolean } = {}, ) { - const hideParamTypes = context.options.getValue("hideParameterTypesInTitle"); const renderParam = hideParamTypes ? renderParameterWithoutType : renderParameterWithType.bind(null, context); return ( diff --git a/src/lib/output/themes/default/partials/member.sources.tsx b/src/lib/output/themes/default/partials/member.sources.tsx index 27bdda522..cb842b098 100644 --- a/src/lib/output/themes/default/partials/member.sources.tsx +++ b/src/lib/output/themes/default/partials/member.sources.tsx @@ -6,7 +6,7 @@ function sourceLink(context: DefaultThemeRenderContext, item: SourceReference) { if (!item.url) { return ( <li> - Defined in {item.fileName}:{item.line} + {context.i18n.theme_defined_in()} {item.fileName}:{item.line} </li> ); } @@ -14,7 +14,7 @@ function sourceLink(context: DefaultThemeRenderContext, item: SourceReference) { if (context.options.getValue("sourceLinkExternal")) { return ( <li> - {"Defined in "} + {context.i18n.theme_defined_in()}{" "} <a href={item.url} class="external" target="_blank"> {item.fileName}:{item.line} </a> @@ -24,7 +24,7 @@ function sourceLink(context: DefaultThemeRenderContext, item: SourceReference) { return ( <li> - {"Defined in "} + {context.i18n.theme_defined_in()}{" "} <a href={item.url}> {item.fileName}:{item.line} </a> @@ -41,24 +41,21 @@ export const memberSources = ( if (props.implementationOf) { sources.push( <p> - {"Implementation of "} - {context.typeAndParent(props.implementationOf)} + {context.i18n.theme_implementation_of()} {context.typeAndParent(props.implementationOf)} </p>, ); } if (props.inheritedFrom) { sources.push( <p> - {"Inherited from "} - {context.typeAndParent(props.inheritedFrom)} + {context.i18n.theme_inherited_from()} {context.typeAndParent(props.inheritedFrom)} </p>, ); } if (props.overwrites) { sources.push( <p> - {"Overrides "} - {context.typeAndParent(props.overwrites)} + {context.i18n.theme_overrides()} {context.typeAndParent(props.overwrites)} </p>, ); } diff --git a/src/lib/output/themes/default/partials/member.tsx b/src/lib/output/themes/default/partials/member.tsx index 6e189d8a5..fd7e5e6ee 100644 --- a/src/lib/output/themes/default/partials/member.tsx +++ b/src/lib/output/themes/default/partials/member.tsx @@ -1,10 +1,10 @@ import { classNames, getDisplayName, wbr } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; -import { JSX } from "../../../../utils"; -import { DeclarationReflection, ReferenceReflection } from "../../../../models"; +import { JSX, Raw } from "../../../../utils"; +import { type DeclarationReflection, type DocumentReflection, ReferenceReflection } from "../../../../models"; import { anchorIcon } from "./anchor-icon"; -export function member(context: DefaultThemeRenderContext, props: DeclarationReflection) { +export function member(context: DefaultThemeRenderContext, props: DeclarationReflection | DocumentReflection) { context.page.pageHeadings.push({ link: `#${props.anchor}`, text: getDisplayName(props), @@ -12,6 +12,26 @@ export function member(context: DefaultThemeRenderContext, props: DeclarationRef classes: context.getReflectionClasses(props), }); + // With the default url derivation, we'll never hit this case as documents are always placed into their + // own pages. Handle it here in case someone creates a custom url scheme which embeds guides within the page. + if (props.isDocument()) { + return ( + <section class={classNames({ "tsd-panel": true, "tsd-member": true }, context.getReflectionClasses(props))}> + <a id={props.anchor} class="tsd-anchor"></a> + {!!props.name && ( + <h3 class="tsd-anchor-link"> + {context.reflectionFlags(props)} + <span class={classNames({ deprecated: props.isDeprecated() })}>{wbr(props.name)}</span> + {anchorIcon(context, props.anchor)} + </h3> + )} + <div class="tsd-comment tsd-typography"> + <Raw html={context.markdown(props.content)} /> + </div> + </section> + ); + } + return ( <section class={classNames({ "tsd-panel": true, "tsd-member": true }, context.getReflectionClasses(props))}> <a id={props.anchor} class="tsd-anchor"></a> @@ -25,10 +45,10 @@ export function member(context: DefaultThemeRenderContext, props: DeclarationRef {props.signatures ? context.memberSignatures(props) : props.hasGetterOrSetter() - ? context.memberGetterSetter(props) - : props instanceof ReferenceReflection - ? context.memberReference(props) - : context.memberDeclaration(props)} + ? context.memberGetterSetter(props) + : props instanceof ReferenceReflection + ? context.memberReference(props) + : context.memberDeclaration(props)} {props.groups?.map((item) => item.children.map((item) => !item.hasOwnDocument && context.member(item)))} </section> diff --git a/src/lib/output/themes/default/partials/members.group.tsx b/src/lib/output/themes/default/partials/members.group.tsx deleted file mode 100644 index cfbad3885..000000000 --- a/src/lib/output/themes/default/partials/members.group.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; -import { JSX } from "../../../../utils"; -import type { ReflectionGroup } from "../../../../models"; - -export function membersGroup(context: DefaultThemeRenderContext, group: ReflectionGroup) { - if (group.categories) { - return ( - <> - {group.categories.map((item) => ( - <section class="tsd-panel-group tsd-member-group"> - <h2> - {group.title} - {!!item.title && <> - {item.title}</>} - </h2> - {item.children.map((item) => !item.hasOwnDocument && context.member(item))} - </section> - ))} - </> - ); - } - - return ( - <section class="tsd-panel-group tsd-member-group"> - <h2>{group.title}</h2> - {group.children.map((item) => !item.hasOwnDocument && context.member(item))} - </section> - ); -} diff --git a/src/lib/output/themes/default/partials/members.tsx b/src/lib/output/themes/default/partials/members.tsx index 9cb0f0f06..51fa596b9 100644 --- a/src/lib/output/themes/default/partials/members.tsx +++ b/src/lib/output/themes/default/partials/members.tsx @@ -1,29 +1,61 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; -import { JSX } from "../../../../utils"; -import { ContainerReflection, DeclarationReflection } from "../../../../models"; -import { classNames } from "../../lib"; +import { filterMap, JSX } from "../../../../utils"; +import type { ContainerReflection } from "../../../../models"; -export function members(context: DefaultThemeRenderContext, props: ContainerReflection) { - if (props.categories && props.categories.length) { - return ( - <> - {props.categories.map( - (item) => - !item.allChildrenHaveOwnDocument() && ( - <section - class={classNames( - { "tsd-panel-group": true, "tsd-member-group": true }, - props instanceof DeclarationReflection ? context.getReflectionClasses(props) : "", - )} - > - <h2>{item.title}</h2> - {item.children.map((item) => !item.hasOwnDocument && context.member(item))} - </section> - ), - )} - </> - ); +function getMemberSections(parent: ContainerReflection) { + if (parent.categories?.length) { + return filterMap(parent.categories, (cat) => { + if (!cat.allChildrenHaveOwnDocument()) { + return { + title: cat.title, + children: cat.children.filter((child) => !child.hasOwnDocument), + }; + } + }); } - return <>{props.groups?.map((item) => !item.allChildrenHaveOwnDocument() && context.membersGroup(item))}</>; + if (parent.groups?.length) { + return parent.groups.flatMap((group) => { + if (group.categories?.length) { + return filterMap(group.categories, (cat) => { + if (!cat.allChildrenHaveOwnDocument()) { + return { + title: `${group.title} - ${cat.title}`, + children: cat.children.filter((child) => !child.hasOwnDocument), + }; + } + }); + } + + return { + title: group.title, + children: group.children.filter((child) => !child.hasOwnDocument), + }; + }); + } + + return []; +} + +export function members(context: DefaultThemeRenderContext, props: ContainerReflection) { + const sections = getMemberSections(props).filter((sect) => sect.children.length); + + return ( + <> + {sections.map(({ title, children }) => { + context.page.startNewSection(title); + + return ( + <details class="tsd-panel-group tsd-member-group tsd-accordion" open> + <summary class="tsd-accordion-summary" data-key={"section-" + title}> + <h2> + {context.icons.chevronDown()} {title} + </h2> + </summary> + <section>{children.map((item) => context.member(item))}</section> + </details> + ); + })} + </> + ); } diff --git a/src/lib/output/themes/default/partials/navigation.tsx b/src/lib/output/themes/default/partials/navigation.tsx index 6622fa70d..d14e86413 100644 --- a/src/lib/output/themes/default/partials/navigation.tsx +++ b/src/lib/output/themes/default/partials/navigation.tsx @@ -1,7 +1,7 @@ -import { Reflection, ReflectionKind } from "../../../../models"; +import { type Reflection, ReflectionFlag, ReflectionKind } from "../../../../models"; import { JSX } from "../../../../utils"; -import type { PageEvent } from "../../../events"; -import { camelToTitleCase, classNames, getDisplayName, wbr } from "../../lib"; +import type { PageEvent, PageHeading } from "../../../events"; +import { classNames, getDisplayName, wbr } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; export function sidebar(context: DefaultThemeRenderContext, props: PageEvent<Reflection>) { @@ -27,16 +27,30 @@ function buildFilterItem(context: DefaultThemeRenderContext, name: string, displ export function sidebarLinks(context: DefaultThemeRenderContext) { const links = Object.entries(context.options.getValue("sidebarLinks")); - if (!links.length) return null; + const navLinks = Object.entries(context.options.getValue("navigationLinks")); + + if (!links.length && !navLinks.length) return null; return ( <nav id="tsd-sidebar-links" class="tsd-navigation"> {links.map(([label, url]) => ( <a href={url}>{label}</a> ))} + {navLinks.map(([label, url]) => ( + <a href={url} class="tsd-nav-link"> + {label} + </a> + ))} </nav> ); } +const flagOptionNameToReflectionFlag = { + protected: ReflectionFlag.Protected, + private: ReflectionFlag.Private, + external: ReflectionFlag.External, + inherited: ReflectionFlag.Inherited, +}; + export function settings(context: DefaultThemeRenderContext) { const defaultFilters = context.options.getValue("visibilityFilters") as Record<string, boolean>; @@ -50,7 +64,12 @@ export function settings(context: DefaultThemeRenderContext) { .toLowerCase(); visibilityOptions.push( - buildFilterItem(context, filterName, camelToTitleCase(key.substring(1)), defaultFilters[key]), + buildFilterItem( + context, + filterName, + context.internationalization.translateTagName(key as `@${string}`), + defaultFilters[key], + ), ); } else if ( (key === "protected" && !context.options.getValue("excludeProtected")) || @@ -58,7 +77,14 @@ export function settings(context: DefaultThemeRenderContext) { (key === "external" && !context.options.getValue("excludeExternals")) || key === "inherited" ) { - visibilityOptions.push(buildFilterItem(context, key, camelToTitleCase(key), defaultFilters[key])); + visibilityOptions.push( + buildFilterItem( + context, + key, + context.internationalization.flagString(flagOptionNameToReflectionFlag[key]), + defaultFilters[key], + ), + ); } } @@ -66,28 +92,28 @@ export function settings(context: DefaultThemeRenderContext) { return ( <div class="tsd-navigation settings"> - <details class="tsd-index-accordion" open={false}> + <details class="tsd-accordion" open={false}> <summary class="tsd-accordion-summary"> <h3> {context.icons.chevronDown()} - Settings + {context.i18n.theme_settings()} </h3> </summary> <div class="tsd-accordion-details"> {visibilityOptions.length && ( <div class="tsd-filter-visibility"> - <h4 class="uppercase">Member Visibility</h4> - <form> - <ul id="tsd-filter-options">{...visibilityOptions}</ul> - </form> + <span class="settings-label">{context.i18n.theme_member_visibility()}</span> + <ul id="tsd-filter-options">{...visibilityOptions}</ul> </div> )} <div class="tsd-theme-toggle"> - <h4 class="uppercase">Theme</h4> + <label class="settings-label" for="tsd-theme"> + {context.i18n.theme_theme()} + </label> <select id="tsd-theme"> - <option value="os">OS</option> - <option value="light">Light</option> - <option value="dark">Dark</option> + <option value="os">{context.i18n.theme_os()}</option> + <option value="light">{context.i18n.theme_light()}</option> + <option value="dark">{context.i18n.theme_dark()}</option> </select> </div> </div> @@ -104,7 +130,7 @@ export const navigation = function navigation(context: DefaultThemeRenderContext <span>{getDisplayName(props.project)}</span> </a> <ul class="tsd-small-nested-navigation" id="tsd-nav-container" data-base={context.relativeURL("./")}> - <li>Loading...</li> + <li>{context.i18n.theme_loading()}</li> </ul> </nav> ); @@ -119,7 +145,7 @@ export function pageSidebar(context: DefaultThemeRenderContext, props: PageEvent ); } -export function pageNavigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>) { +function buildSectionNavigation(context: DefaultThemeRenderContext, headings: PageHeading[]) { const levels: JSX.Element[][] = [[]]; function finalizeLevel(finishedHandlingHeadings: boolean) { @@ -139,8 +165,12 @@ export function pageNavigation(context: DefaultThemeRenderContext, props: PageEv levels[levels.length - 1].push(built); } - for (const heading of props.pageHeadings) { - const inferredLevel = heading.level ? heading.level + 1 : 1; + for (const heading of headings) { + const inferredLevel = heading.level + ? heading.level + 2 // regular heading + : heading.kind + ? 2 // reflection + : 1; // group/category while (inferredLevel < levels.length) { finalizeLevel(false); } @@ -161,22 +191,43 @@ export function pageNavigation(context: DefaultThemeRenderContext, props: PageEv finalizeLevel(true); } - if (!levels[0].length) { + levels.unshift([]); + finalizeLevel(true); + return levels[0]; +} + +export function pageNavigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>) { + if (!props.pageSections.some((sect) => sect.headings.length)) { return <></>; } - levels.unshift([]); - finalizeLevel(true); + const sections: JSX.Children = []; + + for (const section of props.pageSections) { + if (section.title) { + sections.push( + <details open class="tsd-accordion tsd-page-navigation-section"> + <summary class="tsd-accordion-summary" data-key={`tsd-otp-${section.title}`}> + {context.icons.chevronDown()} + {section.title} + </summary> + <div>{buildSectionNavigation(context, section.headings)}</div> + </details>, + ); + } else { + sections.push(buildSectionNavigation(context, section.headings)); + } + } return ( - <details open={true} class="tsd-index-accordion tsd-page-navigation"> + <details open={true} class="tsd-accordion tsd-page-navigation"> <summary class="tsd-accordion-summary"> <h3> {context.icons.chevronDown()} - On This Page + {context.i18n.theme_on_this_page()} </h3> </summary> - <div class="tsd-accordion-details">{levels[0]}</div> + <div class="tsd-accordion-details">{sections}</div> </details> ); } diff --git a/src/lib/output/themes/default/partials/parameter.tsx b/src/lib/output/themes/default/partials/parameter.tsx index be633830a..7a6dd553b 100644 --- a/src/lib/output/themes/default/partials/parameter.tsx +++ b/src/lib/output/themes/default/partials/parameter.tsx @@ -1,7 +1,7 @@ import { classNames, getKindClass, wbr } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX } from "../../../../utils"; -import { DeclarationReflection, ReflectionType } from "../../../../models"; +import { type DeclarationReflection, ReflectionType, type SignatureReflection } from "../../../../models"; export const parameter = (context: DefaultThemeRenderContext, props: DeclarationReflection) => ( <> @@ -26,29 +26,7 @@ export const parameter = (context: DefaultThemeRenderContext, props: Declaration </ul> </li> )} - {!!props.indexSignature && ( - <> - <li class="tsd-parameter-index-signature"> - <h5> - <span class="tsd-signature-symbol">[</span> - {props.indexSignature?.parameters?.map((item) => ( - <> - {!!item.flags.isRest && <span class="tsd-signature-symbol">...</span>} - <span class={getKindClass(item)}>{item.name}</span> - {": "} - {context.type(item.type)} - </> - ))} - <span class="tsd-signature-symbol">{"]: "}</span> - {context.type(props.indexSignature.type)} - </h5> - {context.commentSummary(props.indexSignature)} - {context.commentTags(props.indexSignature)} - {props.indexSignature.type instanceof ReflectionType && - context.parameter(props.indexSignature.type.declaration)} - </li> - </> - )} + {props.indexSignatures?.map((index) => renderParamIndexSignature(context, index))} {props.children?.map((item) => ( <> {item.signatures ? ( @@ -134,3 +112,25 @@ export const parameter = (context: DefaultThemeRenderContext, props: Declaration </ul> </> ); + +function renderParamIndexSignature(context: DefaultThemeRenderContext, index: SignatureReflection) { + return ( + <li class="tsd-parameter-index-signature"> + <h5> + <span class="tsd-signature-symbol">[</span> + {index.parameters!.map((item) => ( + <> + <span class={getKindClass(item)}>{item.name}</span> + {": "} + {context.type(item.type)} + </> + ))} + <span class="tsd-signature-symbol">{"]: "}</span> + {context.type(index.type)} + </h5> + {context.commentSummary(index)} + {context.commentTags(index)} + {index.type instanceof ReflectionType && context.parameter(index.type.declaration)} + </li> + ); +} diff --git a/src/lib/output/themes/default/partials/reflectionPreview.tsx b/src/lib/output/themes/default/partials/reflectionPreview.tsx index 95df3b4f6..ec1fb8bb5 100644 --- a/src/lib/output/themes/default/partials/reflectionPreview.tsx +++ b/src/lib/output/themes/default/partials/reflectionPreview.tsx @@ -7,8 +7,9 @@ export function reflectionPreview(context: DefaultThemeRenderContext, props: Ref if (!(props instanceof DeclarationReflection)) return; // Each property of the interface will have a member rendered later on the page describing it, so generate - // a type-like object with links to each member. - if (props.kindOf(ReflectionKind.Interface)) { + // a type-like object with links to each member. Don't do this if we don't have any children as it will + // generate a broken looking interface. (See TraverseCallback) + if (props.kindOf(ReflectionKind.Interface) && props.children) { return ( <div class="tsd-signature"> <span class="tsd-signature-keyword">interface </span> diff --git a/src/lib/output/themes/default/partials/toolbar.tsx b/src/lib/output/themes/default/partials/toolbar.tsx index 42901e370..03240d323 100644 --- a/src/lib/output/themes/default/partials/toolbar.tsx +++ b/src/lib/output/themes/default/partials/toolbar.tsx @@ -12,7 +12,7 @@ export const toolbar = (context: DefaultThemeRenderContext, props: PageEvent<Ref <label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption"> {context.icons.search()} </label> - <input type="text" id="tsd-search-field" aria-label="Search" /> + <input type="text" id="tsd-search-field" aria-label={context.i18n.theme_search()} /> </div> <div class="field"> @@ -24,8 +24,8 @@ export const toolbar = (context: DefaultThemeRenderContext, props: PageEvent<Ref </div> <ul class="results"> - <li class="state loading">Preparing search index...</li> - <li class="state failure">The search index is not available</li> + <li class="state loading">{context.i18n.theme_preparing_search_index()}</li> + <li class="state failure">{context.i18n.theme_search_index_not_available()}</li> </ul> <a href={context.options.getValue("titleLink") || context.relativeURL("index.html")} class="title"> @@ -34,7 +34,12 @@ export const toolbar = (context: DefaultThemeRenderContext, props: PageEvent<Ref </div> <div class="table-cell" id="tsd-widgets"> - <a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu"> + <a + href="#" + class="tsd-widget tsd-toolbar-icon menu no-caption" + data-toggle="menu" + aria-label={context.i18n.theme_menu()} + > {context.icons.menu()} </a> </div> diff --git a/src/lib/output/themes/default/partials/type.tsx b/src/lib/output/themes/default/partials/type.tsx index c6b7d4fe5..3161332fb 100644 --- a/src/lib/output/themes/default/partials/type.tsx +++ b/src/lib/output/themes/default/partials/type.tsx @@ -1,14 +1,14 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { - DeclarationReflection, + type DeclarationReflection, LiteralType, - ProjectReflection, + type ProjectReflection, ReferenceType, - Reflection, + type Reflection, ReflectionKind, - Type, + type Type, TypeContext, - TypeKindMap, + type TypeKindMap, } from "../../../../models"; import { JSX } from "../../../../utils"; import { getKindClass, join, stringify } from "../../lib"; @@ -69,9 +69,10 @@ function renderUniquePath(context: DefaultThemeRenderContext, reflection: Reflec )); } +const indentSize = 4; let indentationDepth = 0; function includeIndentation(): JSX.Element { - return indentationDepth > 0 ? <span>{"\u00A0".repeat(indentationDepth * 4)}</span> : <></>; + return indentationDepth > 0 ? <span>{"\u00A0".repeat(indentationDepth * indentSize)}</span> : <></>; } export function validateStateIsClean(page: string) { @@ -378,6 +379,7 @@ const typeRenderers: { {context.memberSignatureTitle(sig, { hideName: true, arrowStyle: false, + hideParamTypes: false, })} </>, ); @@ -394,16 +396,17 @@ const typeRenderers: { ); } - if (type.declaration.indexSignature) { - const index = type.declaration.indexSignature; - members.push( - <> - [<span class={getKindClass(type.declaration.indexSignature)}>{index.parameters![0].name}</span>:{" "} - {renderType(context, index.parameters![0].type, TypeContext.none)}] - <span class="tsd-signature-symbol">: </span> - {renderType(context, index.type, TypeContext.none)} - </>, - ); + if (type.declaration.indexSignatures) { + for (const index of type.declaration.indexSignatures) { + members.push( + <> + [<span class={getKindClass(index)}>{index.parameters![0].name}</span>:{" "} + {renderType(context, index.parameters![0].type, TypeContext.none)}] + <span class="tsd-signature-symbol">: </span> + {renderType(context, index.type, TypeContext.none)} + </>, + ); + } } if (!members.length && type.declaration.signatures?.length === 1) { @@ -415,6 +418,7 @@ const typeRenderers: { {context.memberSignatureTitle(type.declaration.signatures[0], { hideName: true, arrowStyle: true, + hideParamTypes: false, })} <span class="tsd-signature-symbol">)</span> </> @@ -422,7 +426,7 @@ const typeRenderers: { } for (const item of type.declaration.signatures || []) { - members.push(context.memberSignatureTitle(item, { hideName: true })); + members.push(context.memberSignatureTitle(item, { hideName: true, hideParamTypes: false })); } if (members.length) { @@ -495,6 +499,31 @@ const typeRenderers: { ); }, union(context, type) { + // This could likely be improved with some print width based heuristic like + // how prettier works, but my initial investigation with it didn't consistently + // produce better results than this much simpler method as the print width + // method doesn't track how far into the current line we are, and I don't want + // to spend the time right now to properly track that here. PR welcome if someone + // wants to take the time to make that a real capability. + // https://gist.github.com/Gerrit0/5cebc127fd4b181e49e354b786d181d7 + if (type.types.length > 3) { + ++indentationDepth; + const membersWithSeparators = type.types.flatMap((item) => [ + includeIndentation(), + <span class="tsd-signature-symbol">| </span>, + renderType(context, item, TypeContext.unionElement), + <br></br>, + ]); + membersWithSeparators.pop(); + --indentationDepth; + + return ( + <> + <br /> + {membersWithSeparators} + </> + ); + } return join(<span class="tsd-signature-symbol"> | </span>, type.types, (item) => renderType(context, item, TypeContext.unionElement), ); diff --git a/src/lib/output/themes/default/partials/typeAndParent.tsx b/src/lib/output/themes/default/partials/typeAndParent.tsx index e776c3c04..6168240eb 100644 --- a/src/lib/output/themes/default/partials/typeAndParent.tsx +++ b/src/lib/output/themes/default/partials/typeAndParent.tsx @@ -1,10 +1,8 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; -import { ArrayType, ReferenceType, SignatureReflection, Type } from "../../../../models"; +import { ArrayType, ReferenceType, SignatureReflection, type Type } from "../../../../models"; import { JSX } from "../../../../utils"; export const typeAndParent = (context: DefaultThemeRenderContext, props: Type): JSX.Element => { - if (!props) return <>void</>; - if (props instanceof ArrayType) { return ( <> @@ -16,12 +14,12 @@ 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; + const parent = refl.parent; return ( <> {parent?.url ? <a href={context.urlTo(parent)}>{parent.name}</a> : parent?.name}. - {refl?.url ? <a href={context.urlTo(refl)}>{refl.name}</a> : refl?.name} + {refl.url ? <a href={context.urlTo(refl)}>{refl.name}</a> : refl.name} </> ); } diff --git a/src/lib/output/themes/default/partials/typeParameters.tsx b/src/lib/output/themes/default/partials/typeParameters.tsx index 9617f1b39..da68da44e 100644 --- a/src/lib/output/themes/default/partials/typeParameters.tsx +++ b/src/lib/output/themes/default/partials/typeParameters.tsx @@ -6,9 +6,9 @@ export function typeParameters(context: DefaultThemeRenderContext, typeParameter return ( <> <section class="tsd-panel"> - <h4>Type Parameters</h4> + <h4>{context.i18n.kind_plural_type_parameter()}</h4> <ul class="tsd-type-parameter-list"> - {typeParameters?.map((item) => ( + {typeParameters.map((item) => ( <li> <span> <a id={item.anchor} class="tsd-anchor"></a> diff --git a/src/lib/output/themes/default/templates/document.tsx b/src/lib/output/themes/default/templates/document.tsx new file mode 100644 index 000000000..5ea74b5e9 --- /dev/null +++ b/src/lib/output/themes/default/templates/document.tsx @@ -0,0 +1,10 @@ +import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; +import type { DocumentReflection } from "../../../../models"; +import type { PageEvent } from "../../../events"; +import { JSX, Raw } from "../../../../utils"; + +export const documentTemplate = ({ markdown }: DefaultThemeRenderContext, props: PageEvent<DocumentReflection>) => ( + <div class="tsd-panel tsd-typography"> + <Raw html={markdown(props.model.content)} /> + </div> +); diff --git a/src/lib/output/themes/default/templates/hierarchy.tsx b/src/lib/output/themes/default/templates/hierarchy.tsx index ee8fbcb1c..0a641acfc 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( export function hierarchyTemplate(context: DefaultThemeRenderContext, props: PageEvent<ProjectReflection>) { return ( <> - <h2>Class Hierarchy</h2> + <h2>{context.i18n.theme_class_hierarchy_title()}</h2> {getHierarchyRoots(props.project).map((root) => ( <ul class="tsd-full-hierarchy">{fullHierarchy(context, root)}</ul> ))} diff --git a/src/lib/output/themes/default/templates/reflection.tsx b/src/lib/output/themes/default/templates/reflection.tsx index 7c5a8f512..8e04c1ad7 100644 --- a/src/lib/output/themes/default/templates/reflection.tsx +++ b/src/lib/output/themes/default/templates/reflection.tsx @@ -1,7 +1,13 @@ -import { classNames, hasTypeParameters } from "../../lib"; +import { classNames, getKindClass, hasTypeParameters } from "../../lib"; import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import type { PageEvent } from "../../../events"; -import { ContainerReflection, DeclarationReflection, ReflectionKind, ReflectionType } from "../../../../models"; +import { + type ContainerReflection, + DeclarationReflection, + ReflectionKind, + ReflectionType, + type SignatureReflection, +} from "../../../../models"; import { JSX, Raw } from "../../../../utils"; export function reflectionTemplate(context: DefaultThemeRenderContext, props: PageEvent<ContainerReflection>) { @@ -37,7 +43,7 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa {!!props.model.implementedTypes && ( <section class="tsd-panel"> - <h4>Implements</h4> + <h4>{context.i18n.theme_implements()}</h4> <ul class="tsd-hierarchy"> {props.model.implementedTypes.map((item) => ( <li>{context.type(item)}</li> @@ -47,7 +53,7 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa )} {!!props.model.implementedBy && ( <section class="tsd-panel"> - <h4>Implemented by</h4> + <h4>{context.i18n.theme_implemented_by()}</h4> <ul class="tsd-hierarchy"> {props.model.implementedBy.map((item) => ( <li>{context.type(item)}</li> @@ -55,26 +61,15 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa </ul> </section> )} - {!!props.model.signatures && ( + {!!props.model.signatures?.length && ( <section class="tsd-panel">{context.memberSignatures(props.model)}</section> )} - {!!props.model.indexSignature && ( + {!!props.model.indexSignatures?.length && ( <section class={classNames({ "tsd-panel": true }, context.getReflectionClasses(props.model))}> - <h4 class="tsd-before-signature">Indexable</h4> - <div class="tsd-signature"> - <span class="tsd-signature-symbol">[</span> - {props.model.indexSignature.parameters!.map((item) => ( - <> - {item.name}: {context.type(item.type)} - </> - ))} - <span class="tsd-signature-symbol">]: </span> - {context.type(props.model.indexSignature.type)} - </div> - {context.commentSummary(props.model.indexSignature)} - {context.commentTags(props.model.indexSignature)} - {props.model.indexSignature?.type instanceof ReflectionType && - context.parameter(props.model.indexSignature.type.declaration)} + <h4 class="tsd-before-signature">{context.i18n.theme_indexable()}</h4> + <ul class="tsd-signatures"> + {props.model.indexSignatures.map((index) => renderIndexSignature(context, index))} + </ul> </section> )} {!props.model.signatures && context.memberSources(props.model)} @@ -85,3 +80,23 @@ export function reflectionTemplate(context: DefaultThemeRenderContext, props: Pa </> ); } + +function renderIndexSignature(context: DefaultThemeRenderContext, index: SignatureReflection) { + return ( + <li class="tsd-index-signature"> + <div class="tsd-signature"> + <span class="tsd-signature-symbol">[</span> + {index.parameters!.map((item) => ( + <> + <span class={getKindClass(item)}>{item.name}</span>: {context.type(item.type)} + </> + ))} + <span class="tsd-signature-symbol">]: </span> + {context.type(index.type)} + </div> + {context.commentSummary(index)} + {context.commentTags(index)} + {index.type instanceof ReflectionType && context.parameter(index.type.declaration)} + </li> + ); +} diff --git a/src/lib/output/themes/lib.tsx b/src/lib/output/themes/lib.tsx index 4c1605521..75e8f019e 100644 --- a/src/lib/output/themes/lib.tsx +++ b/src/lib/output/themes/lib.tsx @@ -3,10 +3,10 @@ import { DeclarationReflection, ProjectReflection, ReferenceReflection, - Reflection, + type Reflection, ReflectionKind, SignatureReflection, - TypeParameterReflection, + type TypeParameterReflection, } from "../../models"; import { JSX } from "../../utils"; @@ -27,7 +27,7 @@ export function getDisplayName(refl: Reflection): string { } export function toStyleClass(str: string): string { - return str.replace(/(\w)([A-Z])/g, (_m, m1, m2) => m1 + "-" + m2).toLowerCase(); + return str.replace(/(\w)([A-Z])/g, (_m, m1: string, m2: string) => m1 + "-" + m2).toLowerCase(); } export function getKindClass(refl: Reflection): string { @@ -139,18 +139,10 @@ export function renderTypeParametersSignature( ); } -export function camelToTitleCase(text: string) { - return text.substring(0, 1).toUpperCase() + text.substring(1).replace(/[a-z][A-Z]/g, (x) => `${x[0]} ${x[1]}`); -} - /** * Renders the reflection name with an additional `?` if optional. */ export function renderName(refl: Reflection) { - if (!refl.name) { - return <em>{wbr(ReflectionKind.singularString(refl.kind))}</em>; - } - if (refl.flags.isOptional) { return <>{wbr(refl.name)}?</>; } diff --git a/src/lib/serialization/deserializer.ts b/src/lib/serialization/deserializer.ts index 45f8e6cc0..4988e439a 100644 --- a/src/lib/serialization/deserializer.ts +++ b/src/lib/serialization/deserializer.ts @@ -4,6 +4,7 @@ import { ArrayType, ConditionalType, DeclarationReflection, + DocumentReflection, IndexedAccessType, InferredType, IntersectionType, @@ -21,13 +22,13 @@ import { Reflection, ReflectionKind, ReflectionType, - ReflectionVariant, + type ReflectionVariant, RestType, SignatureReflection, - SomeType, + type SomeType, TemplateLiteralType, TupleType, - TypeKindMap, + type TypeKindMap, TypeOperatorType, TypeParameterReflection, UnionType, @@ -36,6 +37,7 @@ import { import { insertPrioritySorted } from "../utils/array"; import type { Logger } from "../utils/loggers"; import type { JSONOutput } from "./index"; +import type { FileRegistry } from "../models/FileRegistry"; export interface DeserializerComponent { priority: number; @@ -51,10 +53,10 @@ export class Deserializer { private deferred: Array<(project: ProjectReflection) => void> = []; private deserializers: DeserializerComponent[] = []; private activeReflection: Reflection[] = []; - constructor(private app: Application) {} + constructor(readonly application: Application) {} get logger(): Logger { - return this.app.logger; + return this.application.logger; } reflectionBuilders: { @@ -66,6 +68,9 @@ export class Deserializer { declaration(parent, obj) { return new DeclarationReflection(obj.name, obj.kind, parent); }, + document(parent, obj) { + return new DocumentReflection(obj.name, parent, [], {}); + }, param(parent, obj) { return new ParameterReflection(obj.name, obj.kind, parent); }, @@ -203,7 +208,13 @@ export class Deserializer { }, }; + /** + * Only set when deserializing. + */ + projectRoot!: string; + oldIdToNewId: Record<number, number | undefined> = {}; + oldFileIdToNewFileId: Record<number, number | undefined> = {}; project: ProjectReflection | undefined; addDeserializer(de: DeserializerComponent): void { @@ -217,16 +228,23 @@ export class Deserializer { */ reviveProject( projectObj: JSONOutput.ProjectReflection, - name?: string, + name: string, + projectRoot: string, + registry: FileRegistry, ): ProjectReflection { ok( this.deferred.length === 0, "Deserializer.defer was called when not deserializing", ); - const project = new ProjectReflection(name || projectObj.name); - project.registerReflection(project); + const project = new ProjectReflection( + name || projectObj.name, + registry, + ); + project.registerReflection(project, undefined, undefined); this.project = project; + this.projectRoot = projectRoot; this.oldIdToNewId = { [projectObj.id]: project.id }; + this.oldFileIdToNewFileId = {}; this.fromObject(project, projectObj); const deferred = this.deferred; @@ -246,21 +264,25 @@ export class Deserializer { ); this.project = undefined; + this.projectRoot = undefined!; this.oldIdToNewId = {}; + this.oldFileIdToNewFileId = {}; return project; } reviveProjects( name: string, projects: readonly JSONOutput.ProjectReflection[], + projectRoot: string, + registry: FileRegistry, ): ProjectReflection { if (projects.length === 1) { - return this.reviveProject(projects[0], name); + return this.reviveProject(projects[0], name, projectRoot, registry); } - const project = new ProjectReflection(name); - project.children = []; + const project = new ProjectReflection(name, registry); this.project = project; + this.projectRoot = projectRoot; for (const proj of projects) { ok( @@ -273,9 +295,10 @@ export class Deserializer { ReflectionKind.Module, project, ); - project.registerReflection(projModule); - project.children.push(projModule); + project.registerReflection(projModule, undefined, undefined); + project.addChild(projModule); this.oldIdToNewId = { [proj.id]: projModule.id }; + this.oldFileIdToNewFileId = {}; this.fromObject(projModule, proj); const deferred = this.deferred; @@ -295,7 +318,9 @@ export class Deserializer { } this.oldIdToNewId = {}; + this.oldFileIdToNewFileId = {}; this.project = undefined; + this.projectRoot = undefined!; return project; } @@ -356,7 +381,7 @@ export class Deserializer { obj as never, ); this.oldIdToNewId[obj.id] = result.id; - this.project!.registerReflection(result); + this.project!.registerReflection(result, undefined, undefined); return result as any; } diff --git a/src/lib/serialization/events.ts b/src/lib/serialization/events.ts index d0090dcd5..b78912280 100644 --- a/src/lib/serialization/events.ts +++ b/src/lib/serialization/events.ts @@ -1,4 +1,3 @@ -import { Event } from "../utils/events"; import type { ProjectReflection } from "../models"; import type { ProjectReflection as JSONProjectReflection } from "./schema"; @@ -9,7 +8,7 @@ import type { ProjectReflection as JSONProjectReflection } from "./schema"; * @see {@link Serializer.EVENT_BEGIN} * @see {@link Serializer.EVENT_END} */ -export class SerializeEvent extends Event { +export class SerializeEvent { /** * The project the renderer is currently processing. */ @@ -17,12 +16,7 @@ export class SerializeEvent extends Event { output: JSONProjectReflection | undefined; - constructor( - name: string, - project: ProjectReflection, - output?: JSONProjectReflection, - ) { - super(name); + constructor(project: ProjectReflection, output?: JSONProjectReflection) { this.project = project; this.output = output; } diff --git a/src/lib/serialization/index.ts b/src/lib/serialization/index.ts index 6b91292ee..ffb55aa2f 100644 --- a/src/lib/serialization/index.ts +++ b/src/lib/serialization/index.ts @@ -6,4 +6,4 @@ export { } from "./deserializer"; export { SerializeEvent } from "./events"; export * as JSONOutput from "./schema"; -export { Serializer } from "./serializer"; +export { Serializer, type SerializerEvents } from "./serializer"; diff --git a/src/lib/serialization/schema.ts b/src/lib/serialization/schema.ts index 3709a6d49..f506702a8 100644 --- a/src/lib/serialization/schema.ts +++ b/src/lib/serialization/schema.ts @@ -29,13 +29,16 @@ */ import type * as M from "../models"; +import type { IfInternal } from "../utils"; /** * Describes the mapping from Model types to the corresponding JSON output type. */ -export type ModelToObject<T> = T extends Array<infer U> - ? _ModelToObject<U>[] - : _ModelToObject<T>; +export type ModelToObject<T> = [T] extends [Array<infer U>] + ? ModelToObject<U>[] + : [M.SomeType] extends [T] + ? SomeType + : _ModelToObject<T>; // Order matters here. Some types are subtypes of other types. type _ModelToObject<T> = @@ -43,40 +46,28 @@ type _ModelToObject<T> = T extends Primitive ? T : Required<T> extends Required<M.ReflectionGroup> - ? ReflectionGroup - : Required<T> extends Required<M.ReflectionCategory> - ? ReflectionCategory - : T extends M.SignatureReflection - ? SignatureReflection - : T extends M.ParameterReflection - ? ParameterReflection - : T extends M.DeclarationReflection - ? DeclarationReflection - : T extends M.TypeParameterReflection - ? TypeParameterReflection - : T extends M.ProjectReflection - ? ProjectReflection - : T extends M.ContainerReflection - ? ContainerReflection - : T extends M.ReferenceReflection - ? ReferenceReflection - : T extends M.Reflection - ? Reflection - : // Types - T extends M.SomeType - ? TypeKindMap[T["type"]] - : T extends M.Type - ? SomeType - : // Miscellaneous - T extends M.Comment - ? Comment - : T extends M.CommentTag - ? CommentTag - : T extends M.CommentDisplayPart - ? CommentDisplayPart - : T extends M.SourceReference - ? SourceReference - : never; + ? ReflectionGroup + : Required<T> extends Required<M.ReflectionCategory> + ? ReflectionCategory + : T extends M.ReflectionVariant[keyof M.ReflectionVariant] + ? ReflectionVariantMap[T["variant"]] + : // Types + T extends M.SomeType + ? TypeKindMap[T["type"]] + : T extends M.Type + ? SomeType + : // Miscellaneous + T extends M.Comment + ? Comment + : T extends M.CommentTag + ? CommentTag + : T extends M.CommentDisplayPart + ? CommentDisplayPart + : T extends M.SourceReference + ? SourceReference + : T extends M.FileRegistry + ? FileRegistry + : never; type Primitive = string | number | undefined | null | boolean; @@ -84,8 +75,8 @@ type Primitive = string | number | undefined | null | boolean; type ToSerialized<T> = T extends Primitive ? T : T extends bigint - ? { value: string; negative: boolean } - : ModelToObject<T>; + ? { value: string; negative: boolean } + : ModelToObject<T>; /** * Helper to describe a set of serialized properties. Primitive types are returned @@ -115,9 +106,28 @@ export interface ReflectionCategory // Reflections /** @category Reflections */ -export type SomeReflection = { - [K in keyof M.ReflectionVariant]: ModelToObject<M.ReflectionVariant[K]>; -}[keyof M.ReflectionVariant]; +export interface ReflectionVariantMap { + declaration: DeclarationReflection; + param: ParameterReflection; + project: ProjectReflection; + reference: ReferenceReflection; + signature: SignatureReflection; + typeParam: TypeParameterReflection; + document: DocumentReflection; +} + +/** @category Reflections */ +export type SomeReflection = ReflectionVariantMap[keyof ReflectionVariantMap]; + +/** @category Reflections */ +export interface DocumentReflection + extends Omit<Reflection, "variant">, + S< + M.DocumentReflection, + "variant" | "content" | "relevanceBoost" | "children" + > { + frontmatter: Record<string, unknown>; +} /** @category Reflections */ export interface ReferenceReflection @@ -138,13 +148,14 @@ export interface SignatureReflection | "variant" | "sources" | "parameters" + | "typeParameters" | "type" | "overwrites" | "inheritedFrom" | "implementationOf" > { - // Weird not to call this typeParameters... preserving backwards compatibility for now. - typeParameter?: ModelToObject<M.SignatureReflection["typeParameters"]>; + /** @deprecated in 0.26, replaced with {@link typeParameters} */ + typeParameter?: ModelToObject<M.TypeParameterReflection[]>; } /** @category Reflections */ @@ -165,7 +176,7 @@ export interface DeclarationReflection | "relevanceBoost" | "type" | "signatures" - | "indexSignature" + | "indexSignatures" | "defaultValue" | "overwrites" | "inheritedFrom" @@ -178,7 +189,10 @@ export interface DeclarationReflection | "setSignature" | "typeParameters" | "readme" - > {} + > { + /** @deprecated moved to {@link indexSignatures} with 0.26. */ + indexSignature?: SignatureReflection; +} /** @category Reflections */ export interface TypeParameterReflection @@ -195,13 +209,21 @@ export interface ProjectReflection M.ProjectReflection, "variant" | "packageName" | "packageVersion" | "readme" > { - symbolIdMap: Record<number, ReflectionSymbolId>; + symbolIdMap: + | Record<number, ReflectionSymbolId> + | IfInternal<undefined, never>; + files: FileRegistry; } /** @category Reflections */ export interface ContainerReflection extends Reflection, - S<M.ContainerReflection, "children" | "groups" | "categories"> {} + S< + M.ContainerReflection, + "children" | "documents" | "groups" | "categories" + > { + childrenIncludingDocuments?: number[]; +} /** @category Reflections */ export interface Reflection @@ -212,7 +234,7 @@ export interface Reflection // Types /** @category Types */ -export type SomeType = ModelToObject<M.SomeType>; +export type SomeType = TypeKindMap[keyof TypeKindMap]; /** @category Types */ export type TypeKindMap = { @@ -345,7 +367,9 @@ export interface TypeOperatorType S<M.TypeOperatorType, "type" | "operator" | "target"> {} /** @category Types */ -export interface UnionType extends Type, S<M.UnionType, "type" | "types"> {} +export interface UnionType + extends Type, + S<M.UnionType, "type" | "types" | "elementSummaries"> {} /** @category Types */ export interface UnknownType extends Type, S<M.UnknownType, "type" | "name"> {} @@ -373,11 +397,15 @@ export interface CommentTag extends S<M.CommentTag, "tag" | "name"> { content: CommentDisplayPart[]; } -/** @category Comments */ +/** + * @see {@link M.CommentDisplayPart} + * @category Comments + */ export type CommentDisplayPart = | { kind: "text"; text: string } | { kind: "code"; text: string } - | InlineTagDisplayPart; + | InlineTagDisplayPart + | RelativeLinkDisplayPart; /** * If `target` is a number, it is a reflection ID. If a string, it is a URL. @@ -392,5 +420,29 @@ export interface InlineTagDisplayPart { tsLinkText?: string; } +/** + * This is used for relative links within comments/documents. + * It is used to mark pieces of text which need to be replaced + * to make links work properly. + */ +export interface RelativeLinkDisplayPart { + kind: "relative-link"; + /** + * The original relative text from the parsed comment. + */ + text: string; + /** + * File ID, if present + */ + target?: number; +} + export interface SourceReference extends S<M.SourceReference, "fileName" | "line" | "character" | "url"> {} + +export interface FileRegistry { + /** Relative path according to the serialization root */ + entries: Record<number, string>; + /** File ID to reflection ID */ + reflections: Record<number, number>; +} diff --git a/src/lib/serialization/serializer.ts b/src/lib/serialization/serializer.ts index 9b4998a0f..91fc46414 100644 --- a/src/lib/serialization/serializer.ts +++ b/src/lib/serialization/serializer.ts @@ -6,18 +6,23 @@ import type { ModelToObject } from "./schema"; import type { SerializerComponent } from "./components"; import { insertPrioritySorted } from "../utils/array"; -export class Serializer extends EventDispatcher { +export interface SerializerEvents { + begin: [SerializeEvent]; + end: [SerializeEvent]; +} + +export class Serializer extends EventDispatcher<SerializerEvents> { /** * Triggered when the {@link Serializer} begins transforming a project. - * @event EVENT_BEGIN + * @event */ - static EVENT_BEGIN = "begin"; + static readonly EVENT_BEGIN = "begin"; /** * Triggered when the {@link Serializer} has finished transforming a project. - * @event EVENT_END + * @event */ - static EVENT_END = "end"; + static readonly EVENT_END = "end"; private serializers: SerializerComponent<any>[] = []; @@ -26,7 +31,7 @@ export class Serializer extends EventDispatcher { */ projectRoot!: string; - addSerializer(serializer: SerializerComponent<any>): void { + addSerializer<T extends {}>(serializer: SerializerComponent<T>): void { insertPrioritySorted(this.serializers, serializer); } @@ -71,17 +76,13 @@ export class Serializer extends EventDispatcher { ): ModelToObject<ProjectReflection> { this.projectRoot = projectRoot; - const eventBegin = new SerializeEvent(Serializer.EVENT_BEGIN, value); - this.trigger(eventBegin); + const eventBegin = new SerializeEvent(value); + this.trigger(Serializer.EVENT_BEGIN, eventBegin); const project = this.toObject(value); - const eventEnd = new SerializeEvent( - Serializer.EVENT_END, - value, - project, - ); - this.trigger(eventEnd); + const eventEnd = new SerializeEvent(value, project); + this.trigger(Serializer.EVENT_END, eventEnd); return project; } diff --git a/src/lib/types/shiki/shiki.d.ts b/src/lib/types/shiki/shiki.d.ts new file mode 100644 index 000000000..4034f072c --- /dev/null +++ b/src/lib/types/shiki/shiki.d.ts @@ -0,0 +1,19 @@ +// Shiki 1.x declares a `loadWasm` function which takes these types as input. +// They are declared in the DOM library, but since TypeDoc doesn't use that library, +// we have to declare some shims, intentionally crafted to not be accidentally +// constructed. + +declare namespace WebAssembly { + interface Instance { + __shikiHack: never; + exports: unknown; + } + interface WebAssemblyInstantiatedSource { + __shikiHack: never; + } + type ImportValue = unknown; +} + +interface Response { + __shikiHack: never; +} diff --git a/src/lib/types/ts-internal/index.d.ts b/src/lib/types/ts-internal/index.d.ts index 31b66c0c6..dfee92d49 100644 --- a/src/lib/types/ts-internal/index.d.ts +++ b/src/lib/types/ts-internal/index.d.ts @@ -28,6 +28,12 @@ declare module "typescript" { noCache?: boolean, ): readonly (JSDoc | JSDocTag)[]; + export function getInterfaceBaseTypeNodes( + node: InterfaceDeclaration, + ): NodeArray<ExpressionWithTypeArguments> | undefined; + + export function getAllSuperTypeNodes(node: ts.Node): readonly ts.TypeNode[]; + export interface Signature { thisParameter?: ts.Symbol; } diff --git a/src/lib/utils/array.ts b/src/lib/utils/array.ts index 051127206..ff79d13aa 100644 --- a/src/lib/utils/array.ts +++ b/src/lib/utils/array.ts @@ -1,3 +1,5 @@ +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 earlier in the array. @@ -120,7 +122,7 @@ export function* zip<T extends Iterable<any>[]>( ): Iterable<{ [K in keyof T]: T[K] extends Iterable<infer U> ? U : T[K] }> { const iterators = args.map((x) => x[Symbol.iterator]()); - while (true) { + for (;;) { const next = iterators.map((i) => i.next()); if (next.some((v) => v.done)) { break; @@ -144,3 +146,27 @@ export function filterMap<T, U>( return result; } + +export function firstDefined<T, U>( + array: readonly T[] | undefined, + 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) { + return result; + } + } + return undefined; +} + +export function filter<T>( + array: readonly T[] | undefined, + predicate: (value: T, index: number, array: readonly T[]) => boolean, +): readonly T[] { + return array ? array.filter(predicate) : emptyArray; +} diff --git a/src/lib/utils/component.ts b/src/lib/utils/component.ts index 4c84a55a3..c87d86944 100644 --- a/src/lib/utils/component.ts +++ b/src/lib/utils/component.ts @@ -1,5 +1,5 @@ import type { Application } from "../application"; -import { EventDispatcher, Event, EventMap } from "./events"; +import { EventDispatcher } from "./events"; /** * Exposes a reference to the root Application component. @@ -8,7 +8,8 @@ export interface ComponentHost { readonly application: Application; } -export interface Component extends AbstractComponent<ComponentHost> {} +export interface Component<E extends Record<keyof E, unknown[]> = {}> + extends AbstractComponent<ComponentHost, E> {} export interface ComponentClass< T extends Component, @@ -28,7 +29,7 @@ export interface ComponentOptions { } const childMappings: { - host: ChildableComponent<any, any>; + host: ChildableComponent<any, any, any>; child: Function; }[] = []; @@ -82,34 +83,17 @@ export function Component(options: ComponentOptions) { }; } -export class ComponentEvent extends Event { - owner: ComponentHost; - - component: AbstractComponent<ComponentHost>; - - static ADDED = "componentAdded"; - - static REMOVED = "componentRemoved"; - - constructor( - name: string, - owner: ComponentHost, - component: AbstractComponent<ComponentHost>, - ) { - super(name); - this.owner = owner; - this.component = component; - } -} - /** * Component base class. Has an owner (unless it's the application root component), * can dispatch events to its children, and has access to the root Application component. * * @template O type of component's owner. */ -export abstract class AbstractComponent<O extends ComponentHost> - extends EventDispatcher +export abstract class AbstractComponent< + O extends ComponentHost, + E extends Record<keyof E, unknown[]>, + > + extends EventDispatcher<E> implements ComponentHost { /** @@ -138,19 +122,6 @@ export abstract class AbstractComponent<O extends ComponentHost> // empty default implementation } - protected bubble(name: Event | EventMap | string, ...args: any[]) { - super.trigger(name, ...args); - - if ( - this.owner instanceof AbstractComponent && - this._componentOwner !== null - ) { - this.owner.bubble(name, ...args); - } - - return this; - } - /** * Return the application / root component instance. */ @@ -180,11 +151,9 @@ export abstract class AbstractComponent<O extends ComponentHost> export abstract class ChildableComponent< O extends ComponentHost, C extends Component, -> extends AbstractComponent<O> { - /** - * - */ - private _componentChildren?: { [name: string]: C }; + E extends Record<keyof E, unknown[]>, +> extends AbstractComponent<O, E> { + private _componentChildren?: { [name: string]: C | undefined }; private _defaultComponents?: { [name: string]: ComponentClass<C> }; @@ -211,11 +180,7 @@ export abstract class ChildableComponent< } getComponents(): C[] { - return Object.values(this._componentChildren || {}); - } - - hasComponent(name: string): boolean { - return !!(this._componentChildren || {})[name]; + return Object.values(this._componentChildren || {}) as C[]; } addComponent<T extends C>( @@ -236,36 +201,10 @@ export abstract class ChildableComponent< typeof componentClass === "function" ? new (<ComponentClass<T>>componentClass)(this) : componentClass; - const event = new ComponentEvent( - ComponentEvent.ADDED, - this, - component, - ); - this.bubble(event); this._componentChildren[name] = component; return component; } } - - removeComponent(name: string): C | undefined { - const component = (this._componentChildren || {})[name]; - if (component) { - delete this._componentChildren![name]; - component.stopListening(); - this.bubble( - new ComponentEvent(ComponentEvent.REMOVED, this, component), - ); - return component; - } - } - - removeAllComponents() { - for (const component of Object.values(this._componentChildren || {})) { - component.stopListening(); - } - - this._componentChildren = {}; - } } diff --git a/src/lib/utils/entry-point.ts b/src/lib/utils/entry-point.ts index 3d08865e9..064723d82 100644 --- a/src/lib/utils/entry-point.ts +++ b/src/lib/utils/entry-point.ts @@ -45,14 +45,17 @@ export interface DocumentationEntryPoint { version?: string; } +export interface DocumentEntryPoint { + displayName: string; + path: string; +} + export function getEntryPoints( logger: Logger, options: Options, ): DocumentationEntryPoint[] | undefined { if (!options.isSet("entryPoints")) { - logger.warn( - "No entry points were provided, this is likely a misconfiguration.", - ); + logger.warn(logger.i18n.no_entry_points_provided()); return []; } @@ -93,14 +96,50 @@ export function getEntryPoints( assertNever(strategy); } - if (result && result.length === 0) { - logger.error("Unable to find any entry points. See previous warnings."); + if (result.length === 0) { + logger.error(logger.i18n.unable_to_find_any_entry_points()); return; } return result; } +/** + * Document entry points are markdown documents that the user has requested we include in the project with + * an option rather than a `@document` tag. + * + * @returns A list of `.md` files to include in the documentation as documents. + */ +export function getDocumentEntryPoints( + logger: Logger, + options: Options, +): DocumentEntryPoint[] { + const docGlobs = options.getValue("projectDocuments"); + if (docGlobs.length === 0) { + return []; + } + + const docPaths = expandGlobs(docGlobs, [], logger); + + // We might want to expand this in the future, there are quite a lot of extensions + // that have at some point or another been used for markdown: https://superuser.com/a/285878 + const supportedFileRegex = /\.(md|markdown)$/; + + const expanded = expandInputFiles( + logger, + docPaths, + options, + supportedFileRegex, + ); + const baseDir = options.getValue("basePath") || deriveRootDir(expanded); + return expanded.map((path) => { + return { + displayName: relative(baseDir, path).replace(/\.[^.]+$/, ""), + path, + }; + }); +} + export function getWatchEntryPoints( logger: Logger, options: Options, @@ -132,15 +171,11 @@ export function getWatchEntryPoints( break; case EntryPointStrategy.Packages: - logger.error( - "Watch mode does not support 'packages' style entry points.", - ); + logger.error(logger.i18n.watch_does_not_support_packages_mode()); break; case EntryPointStrategy.Merge: - logger.error( - "Watch mode does not support 'merge' style entry points.", - ); + logger.error(logger.i18n.watch_does_not_support_merge_mode()); break; default: @@ -148,7 +183,7 @@ export function getWatchEntryPoints( } if (result && result.length === 0) { - logger.error("Unable to find any entry points."); + logger.error(logger.i18n.unable_to_find_any_entry_points()); return; } @@ -187,6 +222,7 @@ function getEntryPointsForPaths( ): DocumentationEntryPoint[] { const baseDir = options.getValue("basePath") || deriveRootDir(inputFiles); const entryPoints: DocumentationEntryPoint[] = []; + let expandSuggestion = true; entryLoop: for (const fileOrDir of inputFiles.map(normalizePath)) { const toCheck = [fileOrDir]; @@ -217,14 +253,13 @@ function getEntryPointsForPaths( } } - const suggestion = isDir(fileOrDir) - ? " If you wanted to include files inside this directory, set --entryPointStrategy to expand or specify a glob." - : ""; logger.warn( - `The entry point ${nicePath( - fileOrDir, - )} is not referenced by the 'files' or 'include' option in your tsconfig.${suggestion}`, + logger.i18n.entry_point_0_not_in_program(nicePath(fileOrDir)), ); + if (expandSuggestion && isDir(fileOrDir)) { + expandSuggestion = false; + logger.info(logger.i18n.use_expand_or_glob_for_files_in_dir()); + } } return entryPoints; @@ -236,9 +271,15 @@ export function getExpandedEntryPointsForPaths( options: Options, programs = getEntryPrograms(inputFiles, logger, options), ): DocumentationEntryPoint[] { + const compilerOptions = options.getCompilerOptions(); + const supportedFileRegex = + compilerOptions.allowJs || compilerOptions.checkJs + ? /\.([cm][tj]s|[tj]sx?)$/ + : /\.([cm]ts|tsx?)$/; + return getEntryPointsForPaths( logger, - expandInputFiles(logger, inputFiles, options), + expandInputFiles(logger, inputFiles, options, supportedFileRegex), options, programs, ); @@ -260,15 +301,13 @@ function expandGlobs(inputFiles: string[], exclude: string[], logger: Logger) { if (result.length === 0) { logger.warn( - `The entrypoint glob ${nicePath( - entry, - )} did not match any files.`, + logger.i18n.glob_0_did_not_match_any_files(nicePath(entry)), ); } else if (filtered.length === 0) { logger.warn( - `The entrypoint glob ${nicePath( - entry, - )} did not match any files after applying exclude patterns.`, + logger.i18n.entry_point_0_did_not_match_any_files_after_exclude( + nicePath(entry), + ), ); } else { logger.verbose( @@ -344,16 +383,12 @@ function expandInputFiles( logger: Logger, entryPoints: string[], options: Options, + supportedFile: RegExp, ): string[] { const files: string[] = []; const exclude = createMinimatch(options.getValue("exclude")); - const compilerOptions = options.getCompilerOptions(); - const supportedFileRegex = - compilerOptions.allowJs || compilerOptions.checkJs - ? /\.([cm][tj]s|[tj]sx?)$/ - : /\.([cm]ts|tsx?)$/; function add(file: string, entryPoint: boolean) { let stats: FS.Stats; try { @@ -371,7 +406,7 @@ function expandInputFiles( FS.readdirSync(file).forEach((next) => { add(join(file, next), false); }); - } else if (supportedFileRegex.test(file)) { + } else if (supportedFile.test(file)) { if (!entryPoint && matchesAny(exclude, file)) { return; } @@ -382,9 +417,7 @@ function expandInputFiles( entryPoints.forEach((file) => { const resolved = resolve(file); if (!FS.existsSync(resolved)) { - logger.warn( - `Provided entry point ${file} does not exist and will not be included in the docs.`, - ); + logger.warn(logger.i18n.entry_point_0_did_not_exist(file)); return; } diff --git a/src/lib/utils/enum.ts b/src/lib/utils/enum.ts index 5b0ab7c94..798dffab5 100644 --- a/src/lib/utils/enum.ts +++ b/src/lib/utils/enum.ts @@ -34,6 +34,6 @@ export function getEnumKeys(Enum: {}): string[] { return Object.keys(E).filter((k) => E[E[k]] === k); } -export type EnumKeys<E extends {}> = keyof { - [K in keyof E as number extends E[K] ? K : never]: 1; -}; +export type EnumKeys<E extends {}> = { + [K in keyof E]: number extends E[K] ? K : never; +}[keyof E] & {}; diff --git a/src/lib/utils/events.ts b/src/lib/utils/events.ts index 102785738..0dcce2fa8 100644 --- a/src/lib/utils/events.ts +++ b/src/lib/utils/events.ts @@ -1,672 +1,65 @@ -// Backbone.js 1.2.3 -// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// https://backbonejs.org -// -// The Events object is a typesafe conversion of Backbones Events object: -// https://github.com/jashkenas/backbone/blob/05fde9e201f7e2137796663081105cd6dad12a98/backbone.js#L119-L374 - -// Priority: Higher number makes the listener be called earlier. - -const uniqueId = (function () { - const prefixes: Record<string, number | undefined> = Object.create(null); - return function (prefix: string) { - const unique = prefixes[prefix] || 1; - prefixes[prefix] = unique + 1; - return `${prefix}${unique}`; - }; -})(); - -function once<T extends (..._: any) => any>(cb: T): T { - let hasRun = false; - let result: ReturnType<T>; - return function (this: any, ...args: any) { - if (hasRun === false) { - hasRun = true; - result = cb.apply(this, args); - } - return result; - } as T; -} - -export interface EventCallback extends Function { - _callback?: Function; -} - -interface EventListener { - obj: any; - objId: string; - id: string; - listeningTo: EventListeners; - count: number; -} - -interface EventListeners { - [id: string]: EventListener; -} - -interface EventHandler { - callback: EventCallback; - context: any; - ctx: any; - listening: EventListener; - priority: number; -} - -interface EventHandlers { - [name: string]: EventHandler[]; -} - -export interface EventMap { - [name: string]: EventCallback; -} - -interface EventIteratee<T, U> { - (events: U, name: string, callback: Function | undefined, options: T): U; -} - -interface EventTriggerer { - (events: EventHandler[], args: any[]): void; -} - -interface OnApiOptions { - context: any; - ctx: any; - listening: any; - priority: number; -} - -interface OffApiOptions { - context: any; - listeners: any; -} - -// Regular expression used to split event strings. -const eventSplitter = /\s+/; - -/** - * Iterates over the standard `event, callback` (as well as the fancy multiple - * space-separated events `"change blur", callback` and jQuery-style event - * maps `{event: callback}`). - */ -function eventsApi<T extends {}, U>( - iteratee: EventIteratee<T, U>, - events: U, - name: EventMap | string | undefined, - callback: EventCallback | undefined, - options: T, -): U { - let i = 0, - names: string[]; - - const anyOptions = options as any; - - if (name && typeof name === "object") { - // Handle event maps. - if ( - callback !== void 0 && - "context" in options && - anyOptions["context"] === void 0 - ) { - anyOptions["context"] = callback; - } - - for (names = Object.keys(name); i < names.length; i++) { - events = eventsApi( - iteratee, - events, - names[i], - name[names[i]], - options, - ); - } - } else if (name && typeof name === "string" && eventSplitter.test(name)) { - // Handle space separated event names by delegating them individually. - for (names = name.split(eventSplitter); i < names.length; i++) { - events = iteratee(events, names[i], callback, options); - } - } else { - // Finally, standard events. - events = iteratee(events, <any>name, callback, options); - } - - return events; -} - -/** - * The reducing API that adds a callback to the `events` object. - */ -function onApi( - events: EventHandlers, - name: string, - callback: EventCallback | undefined, - options: OnApiOptions, -): EventHandlers { - if (callback) { - const handlers = (events[name] ||= []); - const context = options.context, - ctx = options.ctx, - listening = options.listening, - priority = options.priority; - if (listening) { - listening.count++; - } - - handlers.push({ - callback: callback, - context: context, - ctx: context || ctx, - listening: listening, - priority: priority, - }); - - handlers.sort((a, b) => b.priority - a.priority); - } - - return events; -} +import { insertPrioritySorted } from "./array"; /** - * The reducing API that removes a callback from the `events` object. - */ -function offApi( - events: EventHandlers | undefined, - name: string, - callback: EventCallback | undefined, - options: OffApiOptions, -): EventHandlers | undefined { - if (!events) { - return; - } - - let i = 0, - listening: EventListener; - const context = options.context, - listeners = options.listeners; - - // Delete all events listeners and "drop" events. - if (!name && !callback && !context) { - const ids = Object.keys(listeners || {}); - for (; i < ids.length; i++) { - listening = listeners[ids[i]]; - delete listeners[listening.id]; - delete listening.listeningTo[listening.objId]; - } - return; - } - - const names = name ? [name] : Object.keys(events || {}); - for (; i < names.length; i++) { - name = names[i]; - const handlers = events[name]; - - // Bail out if there are no events stored. - if (!handlers) { - break; - } - - // Replace events if there are any remaining. Otherwise, clean up. - const remaining: EventHandler[] = []; - for (let j = 0; j < handlers.length; j++) { - const handler = handlers[j]; - if ( - (callback && - callback !== handler.callback && - callback !== handler.callback._callback) || - (context && context !== handler.context) - ) { - remaining.push(handler); - } else { - listening = handler.listening; - if (listening && --listening.count === 0) { - delete listeners[listening.id]; - delete listening.listeningTo[listening.objId]; - } - } - } - - // Update tail event if the list has any events. Otherwise, clean up. - if (remaining.length) { - events[name] = remaining; - } else { - delete events[name]; - } - } - - if (Object.keys(events || {}).length !== 0) { - return events; - } -} - -/** - * Reduces the event callbacks into a map of `{event: onceWrapper`.} - * `offer` unbinds the `onceWrapper` after it has been called. - */ -function onceMap( - map: EventMap, - name: string, - callback: EventCallback | undefined, - offer: Function, -): EventMap { - if (callback) { - const listener: EventCallback = (map[name] = once(function ( - this: any, - ...args: any - ) { - offer(name, listener); - callback.apply(this, args); - })); - - listener._callback = callback; - } - - return map; -} - -/** - * Handles triggering the appropriate event callbacks. - */ -function triggerApi( - objEvents: EventHandlers, - name: string, - _callback: Function | undefined, - args: any[], - triggerer: EventTriggerer = triggerEvents, -): EventHandlers { - if (objEvents) { - const events = objEvents[name]; - let allEvents = objEvents["all"]; - if (events && allEvents) { - allEvents = allEvents.slice(); - } - if (events) { - triggerer(events, args); - } - if (allEvents) { - triggerer(allEvents, [name].concat(args)); - } - } - - return objEvents; -} - -/** - * A difficult-to-believe, but optimized internal dispatch function for - * triggering events. Tries to keep the usual cases speedy (most internal - * Backbone events have 3 arguments). - */ -function triggerEvents(events: EventHandler[], args: any[]) { - let ev: EventHandler, - i = -1; - const l = events.length, - a1 = args[0], - a2 = args[1], - a3 = args[2]; - switch (args.length) { - case 0: - while (++i < l) { - (ev = events[i]).callback.call(ev.ctx); - } - return; - case 1: - while (++i < l) { - (ev = events[i]).callback.call(ev.ctx, a1); - } - return; - case 2: - while (++i < l) { - (ev = events[i]).callback.call(ev.ctx, a1, a2); - } - return; - case 3: - while (++i < l) { - (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); - } - return; - default: - while (++i < l) { - (ev = events[i]).callback.apply(ev.ctx, args); - } - return; - } -} - -/** - * An event object. - */ -export class Event { - /** - * The name of the event. - */ - private _name: string; - - /** - * Has {@link Event.stopPropagation} been called? - */ - private _isPropagationStopped = false; - - /** - * Has {@link Event.preventDefault} been called? - */ - private _isDefaultPrevented = false; - - /** - * Create a new Event instance. - */ - constructor(name: string) { - this._name = name; - } - - /** - * Stop the propagation of this event. Remaining event handlers will not be executed. - */ - stopPropagation() { - this._isPropagationStopped = true; - } - - /** - * Prevent the default action associated with this event from being executed. - */ - preventDefault() { - this._isDefaultPrevented = true; - } - - /** - * Return the event name. - */ - get name(): string { - return this._name; - } - - /** - * Has {@link Event.stopPropagation} been called? - */ - get isPropagationStopped(): boolean { - return this._isPropagationStopped; - } - - /** - * Has {@link Event.preventDefault} been called? - */ - get isDefaultPrevented(): boolean { - return this._isDefaultPrevented; - } -} - -/** - * A class that provides a custom event channel. + * Intentionally very simple event emitter. * - * You may bind a callback to an event with `on` or remove with `off`; - * `trigger`-ing an event fires all callbacks in succession. + * @privateRemarks + * This is essentially a stripped down copy of EventHooks in hooks.ts. */ -export class EventDispatcher { - /** - * Map of all handlers registered with the "on" function. - */ - private _events?: EventHandlers; - - /** - * Map of all objects this instance is listening to. - */ - private _listeningTo?: EventListeners; - - /** - * Map of all objects that are listening to this instance. - */ - private _listeners?: EventListeners; - - /** - * A unique id that identifies this instance. - */ - private get _listenId(): string { - return this._savedListenId || (this._savedListenId = uniqueId("l")); - } - private _savedListenId?: string; - - /** - * Bind an event to a `callback` function. Passing `"all"` will bind - * the callback to all events fired. - */ - on(eventMap: EventMap, context?: any): this; - on( - eventMap: EventMap, - callback?: EventCallback, - context?: any, - priority?: number, - ): this; - on( - name: string, - callback: EventCallback, - context?: any, - priority?: number, - ): this; - on( - nameOrMap: EventMap | string, - callback: EventCallback, - context?: any, - priority?: number, - ) { - this.internalOn(nameOrMap, callback, context, priority); - return this; - } - - /** - * Guard the `listening` argument from the public API. - */ - private internalOn( - name: EventMap | string, - callback: EventCallback | undefined, - context?: any, +export class EventDispatcher<T extends Record<keyof T, unknown[]>> { + // Function is *usually* not a good type to use, but here it lets us specify stricter + // contracts in the methods while not casting everywhere this is used. + private _listeners = new Map< + keyof T, + { + listener: Function; + priority: number; + }[] + >(); + + /** + * Starts listening to an event. + * @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. + */ + on<K extends keyof T>( + event: K, + listener: (this: undefined, ...args: T[K]) => void, priority = 0, - listening?: EventListener, - ) { - this._events = eventsApi( - onApi, - this._events || <EventHandlers>{}, - name, - callback, - { - context: context, - ctx: this, - listening: listening, - priority: priority, - }, - ); - - if (listening) { - const listeners = this._listeners || (this._listeners = {}); - listeners[listening.id] = listening; - } - } - - /** - * Bind an event to only be triggered a single time. After the first time - * the callback is invoked, its listener will be removed. If multiple events - * are passed in using the space-separated syntax, the handler will fire - * once for each event, not once for a combination of all events. - */ - once(eventMap: EventMap, context?: any): this; - once( - name: string, - callback: EventCallback, - context?: any, - priority?: any, - ): this; - once( - name: EventMap | string, - callback?: EventCallback, - context?: any, - priority?: number, - ) { - // Map the event into a `{event: once}` object. - const events = eventsApi( - onceMap, - <EventMap>{}, - name, - callback, - this.off.bind(this), - ); - return this.on(events, void 0, context, priority); - } - - /** - * Remove one or many callbacks. If `context` is null, removes all - * callbacks with that function. If `callback` is null, removes all - * callbacks for the event. If `name` is null, removes all bound - * callbacks for all events. - */ - off(): this; - off(eventMap: EventMap | undefined, context?: any): this; - off( - name: string | undefined, - callback?: EventCallback, - context?: any, - ): this; - off(name?: EventMap | string, callback?: EventCallback, context?: any) { - if (!this._events) { - return this; - } - - this._events = eventsApi(offApi, this._events, name, callback, { - context: context, - listeners: this._listeners, - }); - - return this; - } - - /** - * Inversion-of-control versions of `on`. Tell *this* object to listen to - * an event in another object... keeping track of what it's listening to - * for easier unbinding later. - */ - listenTo( - obj: EventDispatcher, - name: EventMap | string, - callback?: EventCallback, - priority?: number, - ) { - if (!obj) { - return this; - } - const id = obj._listenId; - const listeningTo = this._listeningTo || (this._listeningTo = {}); - let listening = listeningTo[id]; - - // This object is not listening to any other events on `obj` yet. - // Setup the necessary references to track the listening callbacks. - if (!listening) { - const thisId = this._listenId; - listening = listeningTo[id] = { - obj: obj, - objId: id, - id: thisId, - listeningTo: listeningTo, - count: 0, - }; - } - - // Bind callbacks on obj, and keep track of them on listening. - obj.internalOn(name, callback, this, priority, listening); - return this; - } - - /** - * Inversion-of-control versions of `once`. - */ - listenToOnce(obj: EventDispatcher, eventMap: EventMap): this; - listenToOnce( - obj: EventDispatcher, - name: string, - callback: EventCallback, - priority?: number, - ): this; - listenToOnce( - obj: EventDispatcher, - name: EventMap | string, - callback?: EventCallback, - priority?: number, - ) { - // Map the event into a `{event: once}` object. - const events = eventsApi( - onceMap, - <EventMap>{}, - name, - callback, - this.stopListening.bind(this, obj), - ); - return this.listenTo(obj, events, void 0, priority); - } - - /** - * Tell this object to stop listening to either specific events ... or - * to every object it's currently listening to. - */ - stopListening( - obj?: EventDispatcher, - name?: EventMap | string, - callback?: EventCallback, - ) { - const listeningTo = this._listeningTo; - if (!listeningTo) { - return this; - } - - const ids = obj ? [obj._listenId] : Object.keys(listeningTo); - for (let i = 0; i < ids.length; i++) { - const listening = listeningTo[ids[i]]; - - // If listening doesn't exist, this object is not currently - // listening to obj. Break out early. - if (!listening) { - break; + ): void { + const list = (this._listeners.get(event) || []).slice(); + insertPrioritySorted(list, { listener, priority }); + this._listeners.set(event, list); + } + + /** + * Stops listening to an event. + * @param event the event to stop listening to. + * @param listener the function to remove from the listener array. + */ + off<K extends keyof T>( + event: K, + listener: (this: undefined, ...args: T[K]) => void, + ): void { + const listeners = this._listeners.get(event); + if (listeners) { + const index = listeners.findIndex((lo) => lo.listener === listener); + if (index > -1) { + listeners.splice(index, 1); } - - listening.obj.off(name, callback, this); } - - if (Object.keys(listeningTo).length === 0) { - this._listeningTo = void 0; - } - - return this; } /** - * Trigger one or many events, firing all bound callbacks. Callbacks are - * passed the same arguments as `trigger` is, apart from the event name - * (unless you're listening on `"all"`, which will cause your callback to - * receive the true name of the event as the first argument). + * Emits an event to all currently subscribed listeners. + * @param event the event to emit. + * @param args any arguments required for the event. */ - trigger(name: Event | EventMap | string, ...args: any[]) { - if (!this._events) { - return this; + trigger<K extends keyof T>(event: K, ...args: T[K]): void { + const listeners = this._listeners.get(event)?.slice() || []; + for (const { listener } of listeners) { + listener(...args); } - - if (name instanceof Event) { - triggerApi( - this._events, - name.name, - void 0, - [name], - (events: EventHandler[], args: any[]) => { - let ev: EventHandler, - i = -1; - const l = events.length; - while (++i < l) { - if (name.isPropagationStopped) { - return; - } - ev = events[i]; - ev.callback.apply(ev.ctx, args); - } - }, - ); - } else { - eventsApi(triggerApi, this._events, name, void 0, args); - } - - return this; } } diff --git a/src/lib/utils/general.ts b/src/lib/utils/general.ts index 35949f119..7108a4cf1 100644 --- a/src/lib/utils/general.ts +++ b/src/lib/utils/general.ts @@ -10,7 +10,7 @@ import * as Util from "util"; type InternalOnly = true; /** - * Helper type to convert `T` to `F` if strict mode is on. + * Helper type to convert `T` to `F` if compiling TypeDoc with stricter types. * * Can be used in overloads to map a parameter type to `never`. For example, the * following function will work with any string argument, but to improve the type safety @@ -23,14 +23,15 @@ type InternalOnly = true; * * ```ts * function over(flag: 'a' | 'b'): string - * function over(flag: IfStrict<string, never>): string + * // deprecated + * function over(flag: IfInternal<never, string>): string * function over(flag: string): string { return flag } * ``` */ export type IfInternal<T, F> = InternalOnly extends true ? T : F; /** - * Helper type to convert `T` to `never` if strict mode is on. + * Helper type to convert `T` to `never` if compiling TypeDoc with stricter types. * * See {@link IfInternal} for the rationale. */ @@ -54,6 +55,13 @@ export function assertNever(x: never): never { ); } +export function camelToTitleCase(text: string) { + return ( + text.substring(0, 1).toUpperCase() + + text.substring(1).replace(/[a-z][A-Z]/g, (x) => `${x[0]} ${x[1]}`) + ); +} + export function NonEnumerable( _cls: unknown, context: ClassFieldDecoratorContext, diff --git a/src/lib/utils/highlighter.tsx b/src/lib/utils/highlighter.tsx index 5dda79043..663b50dcf 100644 --- a/src/lib/utils/highlighter.tsx +++ b/src/lib/utils/highlighter.tsx @@ -1,74 +1,69 @@ -import { ok as assert } from "assert"; -import { BUNDLED_LANGUAGES, getHighlighter, Highlighter, Theme } from "shiki"; -import { unique, zip } from "./array"; +import { ok as assert, ok } from "assert"; +import type { + BundledLanguage, + BundledTheme, + Highlighter, + TokenStyles, +} from "shiki" with { "resolution-mode": "import" }; import * as JSX from "./jsx"; +import { unique } from "./array"; const aliases = new Map<string, string>(); -for (const lang of BUNDLED_LANGUAGES) { - for (const alias of lang.aliases || []) { - aliases.set(alias, lang.id); +let supportedLanguagesWithoutAliases: string[] = []; +let supportedLanguages: string[] = []; +let supportedThemes: string[] = []; + +export async function loadShikiMetadata() { + if (aliases.size) return; + + const shiki = await import("shiki"); + for (const lang of shiki.bundledLanguagesInfo) { + for (const alias of lang.aliases || []) { + aliases.set(alias, lang.id); + } } -} -const supportedLanguages = unique(["text", ...aliases.keys(), ...BUNDLED_LANGUAGES.map((lang) => lang.id)]).sort(); + supportedLanguages = unique([ + "text", + ...aliases.keys(), + ...shiki.bundledLanguagesInfo.map((lang) => lang.id), + ]).sort(); + + supportedLanguagesWithoutAliases = unique(["text", ...shiki.bundledLanguagesInfo.map((lang) => lang.id)]); + + supportedThemes = Object.keys(shiki.bundledThemes); +} class DoubleHighlighter { private schemes = new Map<string, string>(); constructor( private highlighter: Highlighter, - private light: Theme, - private dark: Theme, + private light: BundledTheme, + private dark: BundledTheme, ) {} - highlight(code: string, lang: string) { - const lightTokens = this.highlighter.codeToThemedTokens(code, lang, this.light, { includeExplanation: false }); - const darkTokens = this.highlighter.codeToThemedTokens(code, lang, this.dark, { includeExplanation: false }); + supports(lang: string) { + return this.highlighter.getLoadedLanguages().includes(lang); + } - // If this fails... something went *very* wrong. - assert(lightTokens.length === darkTokens.length); + highlight(code: string, lang: string) { + const tokens = this.highlighter.codeToTokensWithThemes(code, { + themes: { light: this.light, dark: this.dark }, + lang: lang as BundledLanguage, + }); const docEls: JSX.Element[] = []; - for (const [lightLine, darkLine] of zip(lightTokens, darkTokens)) { - // Different themes can have different rules for when colors change... so unfortunately we have to deal with different - // sets of tokens.Example: light_plus and dark_plus tokenize " = " differently in the `schemes` - // declaration for this file. - - while (lightLine.length && darkLine.length) { - // Simple case, same token. - if (lightLine[0].content === darkLine[0].content) { - docEls.push( - <span class={this.getClass(lightLine[0].color, darkLine[0].color)}> - {lightLine[0].content} - </span>, - ); - lightLine.shift(); - darkLine.shift(); - continue; - } - - if (lightLine[0].content.length < darkLine[0].content.length) { - docEls.push( - <span class={this.getClass(lightLine[0].color, darkLine[0].color)}> - {lightLine[0].content} - </span>, - ); - darkLine[0].content = darkLine[0].content.substring(lightLine[0].content.length); - lightLine.shift(); - continue; - } - - docEls.push( - <span class={this.getClass(lightLine[0].color, darkLine[0].color)}>{darkLine[0].content}</span>, - ); - lightLine[0].content = lightLine[0].content.substring(darkLine[0].content.length); - darkLine.shift(); + for (const line of tokens) { + for (const token of line) { + docEls.push(<span class={this.getClass(token.variants)}>{token.content}</span>); } docEls.push(<br />); } + docEls.pop(); // Remove last <br> docEls.pop(); // Remove last <br> return JSX.renderElement(<>{docEls}</>); @@ -121,8 +116,8 @@ class DoubleHighlighter { return style.join("\n"); } - private getClass(lightColor?: string, darkColor?: string): string { - const key = `${lightColor} | ${darkColor}`; + private getClass(variants: Record<string, TokenStyles>): string { + const key = `${variants["light"].color} | ${variants["dark"].color}`; let scheme = this.schemes.get(key); if (scheme == null) { scheme = `hl-${this.schemes.size}`; @@ -134,9 +129,11 @@ class DoubleHighlighter { let highlighter: DoubleHighlighter | undefined; -export async function loadHighlighter(lightTheme: Theme, darkTheme: Theme) { +export async function loadHighlighter(lightTheme: BundledTheme, darkTheme: BundledTheme, langs: BundledLanguage[]) { if (highlighter) return; - const hl = await getHighlighter({ themes: [lightTheme, darkTheme] }); + + const shiki = await import("shiki"); + const hl = await shiki.createHighlighter({ themes: [lightTheme, darkTheme], langs }); highlighter = new DoubleHighlighter(hl, lightTheme, darkTheme); } @@ -145,14 +142,26 @@ export function isSupportedLanguage(lang: string) { } export function getSupportedLanguages(): string[] { + ok(supportedLanguages.length > 0, "loadShikiMetadata has not been called"); + return supportedLanguages; +} + +export function getSupportedLanguagesWithoutAliases(): string[] { + ok(supportedLanguagesWithoutAliases.length > 0, "loadShikiMetadata has not been called"); return supportedLanguages; } +export function getSupportedThemes(): string[] { + ok(supportedThemes.length > 0, "loadShikiMetadata has not been called"); + return supportedThemes; +} + +export function isLoadedLanguage(lang: string): boolean { + return lang === "text" || (highlighter?.supports(lang) ?? false); +} + export function highlight(code: string, lang: string): string { assert(highlighter, "Tried to highlight with an uninitialized highlighter"); - if (!isSupportedLanguage(lang)) { - return code; - } if (lang === "text") { return JSX.renderElement(<>{code}</>); diff --git a/src/lib/utils/html-entities.json b/src/lib/utils/html-entities.json index 4715c8fb4..de089ea6a 100644 --- a/src/lib/utils/html-entities.json +++ b/src/lib/utils/html-entities.json @@ -1,2233 +1,2326 @@ { - "AElig": "\u00C6", - "AElig;": "\u00C6", - "AMP": "\u0026", - "AMP;": "\u0026", - "Aacute": "\u00C1", - "Aacute;": "\u00C1", - "Abreve;": "\u0102", - "Acirc": "\u00C2", - "Acirc;": "\u00C2", - "Acy;": "\u0410", - "Afr;": "\uD835\uDD04", - "Agrave": "\u00C0", - "Agrave;": "\u00C0", - "Alpha;": "\u0391", - "Amacr;": "\u0100", - "And;": "\u2A53", - "Aogon;": "\u0104", - "Aopf;": "\uD835\uDD38", - "ApplyFunction;": "\u2061", - "Aring": "\u00C5", - "Aring;": "\u00C5", - "Ascr;": "\uD835\uDC9C", - "Assign;": "\u2254", - "Atilde": "\u00C3", - "Atilde;": "\u00C3", - "Auml": "\u00C4", - "Auml;": "\u00C4", - "Backslash;": "\u2216", - "Barv;": "\u2AE7", - "Barwed;": "\u2306", - "Bcy;": "\u0411", - "Because;": "\u2235", - "Bernoullis;": "\u212C", - "Beta;": "\u0392", - "Bfr;": "\uD835\uDD05", - "Bopf;": "\uD835\uDD39", - "Breve;": "\u02D8", - "Bscr;": "\u212C", - "Bumpeq;": "\u224E", - "CHcy;": "\u0427", - "COPY": "\u00A9", - "COPY;": "\u00A9", - "Cacute;": "\u0106", - "Cap;": "\u22D2", - "CapitalDifferentialD;": "\u2145", - "Cayleys;": "\u212D", - "Ccaron;": "\u010C", - "Ccedil": "\u00C7", - "Ccedil;": "\u00C7", - "Ccirc;": "\u0108", - "Cconint;": "\u2230", - "Cdot;": "\u010A", - "Cedilla;": "\u00B8", - "CenterDot;": "\u00B7", - "Cfr;": "\u212D", - "Chi;": "\u03A7", - "CircleDot;": "\u2299", - "CircleMinus;": "\u2296", - "CirclePlus;": "\u2295", - "CircleTimes;": "\u2297", - "ClockwiseContourIntegral;": "\u2232", - "CloseCurlyDoubleQuote;": "\u201D", - "CloseCurlyQuote;": "\u2019", - "Colon;": "\u2237", - "Colone;": "\u2A74", - "Congruent;": "\u2261", - "Conint;": "\u222F", - "ContourIntegral;": "\u222E", - "Copf;": "\u2102", - "Coproduct;": "\u2210", - "CounterClockwiseContourIntegral;": "\u2233", - "Cross;": "\u2A2F", - "Cscr;": "\uD835\uDC9E", - "Cup;": "\u22D3", - "CupCap;": "\u224D", - "DD;": "\u2145", - "DDotrahd;": "\u2911", - "DJcy;": "\u0402", - "DScy;": "\u0405", - "DZcy;": "\u040F", - "Dagger;": "\u2021", - "Darr;": "\u21A1", - "Dashv;": "\u2AE4", - "Dcaron;": "\u010E", - "Dcy;": "\u0414", - "Del;": "\u2207", - "Delta;": "\u0394", - "Dfr;": "\uD835\uDD07", - "DiacriticalAcute;": "\u00B4", - "DiacriticalDot;": "\u02D9", - "DiacriticalDoubleAcute;": "\u02DD", - "DiacriticalGrave;": "\u0060", - "DiacriticalTilde;": "\u02DC", - "Diamond;": "\u22C4", - "DifferentialD;": "\u2146", - "Dopf;": "\uD835\uDD3B", - "Dot;": "\u00A8", - "DotDot;": "\u20DC", - "DotEqual;": "\u2250", - "DoubleContourIntegral;": "\u222F", - "DoubleDot;": "\u00A8", - "DoubleDownArrow;": "\u21D3", - "DoubleLeftArrow;": "\u21D0", - "DoubleLeftRightArrow;": "\u21D4", - "DoubleLeftTee;": "\u2AE4", - "DoubleLongLeftArrow;": "\u27F8", - "DoubleLongLeftRightArrow;": "\u27FA", - "DoubleLongRightArrow;": "\u27F9", - "DoubleRightArrow;": "\u21D2", - "DoubleRightTee;": "\u22A8", - "DoubleUpArrow;": "\u21D1", - "DoubleUpDownArrow;": "\u21D5", - "DoubleVerticalBar;": "\u2225", - "DownArrow;": "\u2193", - "DownArrowBar;": "\u2913", - "DownArrowUpArrow;": "\u21F5", - "DownBreve;": "\u0311", - "DownLeftRightVector;": "\u2950", - "DownLeftTeeVector;": "\u295E", - "DownLeftVector;": "\u21BD", - "DownLeftVectorBar;": "\u2956", - "DownRightTeeVector;": "\u295F", - "DownRightVector;": "\u21C1", - "DownRightVectorBar;": "\u2957", - "DownTee;": "\u22A4", - "DownTeeArrow;": "\u21A7", - "Downarrow;": "\u21D3", - "Dscr;": "\uD835\uDC9F", - "Dstrok;": "\u0110", - "ENG;": "\u014A", - "ETH": "\u00D0", - "ETH;": "\u00D0", - "Eacute": "\u00C9", - "Eacute;": "\u00C9", - "Ecaron;": "\u011A", - "Ecirc": "\u00CA", - "Ecirc;": "\u00CA", - "Ecy;": "\u042D", - "Edot;": "\u0116", - "Efr;": "\uD835\uDD08", - "Egrave": "\u00C8", - "Egrave;": "\u00C8", - "Element;": "\u2208", - "Emacr;": "\u0112", - "EmptySmallSquare;": "\u25FB", - "EmptyVerySmallSquare;": "\u25AB", - "Eogon;": "\u0118", - "Eopf;": "\uD835\uDD3C", - "Epsilon;": "\u0395", - "Equal;": "\u2A75", - "EqualTilde;": "\u2242", - "Equilibrium;": "\u21CC", - "Escr;": "\u2130", - "Esim;": "\u2A73", - "Eta;": "\u0397", - "Euml": "\u00CB", - "Euml;": "\u00CB", - "Exists;": "\u2203", - "ExponentialE;": "\u2147", - "Fcy;": "\u0424", - "Ffr;": "\uD835\uDD09", - "FilledSmallSquare;": "\u25FC", - "FilledVerySmallSquare;": "\u25AA", - "Fopf;": "\uD835\uDD3D", - "ForAll;": "\u2200", - "Fouriertrf;": "\u2131", - "Fscr;": "\u2131", - "GJcy;": "\u0403", - "GT": "\u003E", - "GT;": "\u003E", - "Gamma;": "\u0393", - "Gammad;": "\u03DC", - "Gbreve;": "\u011E", - "Gcedil;": "\u0122", - "Gcirc;": "\u011C", - "Gcy;": "\u0413", - "Gdot;": "\u0120", - "Gfr;": "\uD835\uDD0A", - "Gg;": "\u22D9", - "Gopf;": "\uD835\uDD3E", - "GreaterEqual;": "\u2265", - "GreaterEqualLess;": "\u22DB", - "GreaterFullEqual;": "\u2267", - "GreaterGreater;": "\u2AA2", - "GreaterLess;": "\u2277", - "GreaterSlantEqual;": "\u2A7E", - "GreaterTilde;": "\u2273", - "Gscr;": "\uD835\uDCA2", - "Gt;": "\u226B", - "HARDcy;": "\u042A", - "Hacek;": "\u02C7", - "Hat;": "\u005E", - "Hcirc;": "\u0124", - "Hfr;": "\u210C", - "HilbertSpace;": "\u210B", - "Hopf;": "\u210D", - "HorizontalLine;": "\u2500", - "Hscr;": "\u210B", - "Hstrok;": "\u0126", - "HumpDownHump;": "\u224E", - "HumpEqual;": "\u224F", - "IEcy;": "\u0415", - "IJlig;": "\u0132", - "IOcy;": "\u0401", - "Iacute": "\u00CD", - "Iacute;": "\u00CD", - "Icirc": "\u00CE", - "Icirc;": "\u00CE", - "Icy;": "\u0418", - "Idot;": "\u0130", - "Ifr;": "\u2111", - "Igrave": "\u00CC", - "Igrave;": "\u00CC", - "Im;": "\u2111", - "Imacr;": "\u012A", - "ImaginaryI;": "\u2148", - "Implies;": "\u21D2", - "Int;": "\u222C", - "Integral;": "\u222B", - "Intersection;": "\u22C2", - "InvisibleComma;": "\u2063", - "InvisibleTimes;": "\u2062", - "Iogon;": "\u012E", - "Iopf;": "\uD835\uDD40", - "Iota;": "\u0399", - "Iscr;": "\u2110", - "Itilde;": "\u0128", - "Iukcy;": "\u0406", - "Iuml": "\u00CF", - "Iuml;": "\u00CF", - "Jcirc;": "\u0134", - "Jcy;": "\u0419", - "Jfr;": "\uD835\uDD0D", - "Jopf;": "\uD835\uDD41", - "Jscr;": "\uD835\uDCA5", - "Jsercy;": "\u0408", - "Jukcy;": "\u0404", - "KHcy;": "\u0425", - "KJcy;": "\u040C", - "Kappa;": "\u039A", - "Kcedil;": "\u0136", - "Kcy;": "\u041A", - "Kfr;": "\uD835\uDD0E", - "Kopf;": "\uD835\uDD42", - "Kscr;": "\uD835\uDCA6", - "LJcy;": "\u0409", - "LT": "\u003C", - "LT;": "\u003C", - "Lacute;": "\u0139", - "Lambda;": "\u039B", - "Lang;": "\u27EA", - "Laplacetrf;": "\u2112", - "Larr;": "\u219E", - "Lcaron;": "\u013D", - "Lcedil;": "\u013B", - "Lcy;": "\u041B", - "LeftAngleBracket;": "\u27E8", - "LeftArrow;": "\u2190", - "LeftArrowBar;": "\u21E4", - "LeftArrowRightArrow;": "\u21C6", - "LeftCeiling;": "\u2308", - "LeftDoubleBracket;": "\u27E6", - "LeftDownTeeVector;": "\u2961", - "LeftDownVector;": "\u21C3", - "LeftDownVectorBar;": "\u2959", - "LeftFloor;": "\u230A", - "LeftRightArrow;": "\u2194", - "LeftRightVector;": "\u294E", - "LeftTee;": "\u22A3", - "LeftTeeArrow;": "\u21A4", - "LeftTeeVector;": "\u295A", - "LeftTriangle;": "\u22B2", - "LeftTriangleBar;": "\u29CF", - "LeftTriangleEqual;": "\u22B4", - "LeftUpDownVector;": "\u2951", - "LeftUpTeeVector;": "\u2960", - "LeftUpVector;": "\u21BF", - "LeftUpVectorBar;": "\u2958", - "LeftVector;": "\u21BC", - "LeftVectorBar;": "\u2952", - "Leftarrow;": "\u21D0", - "Leftrightarrow;": "\u21D4", - "LessEqualGreater;": "\u22DA", - "LessFullEqual;": "\u2266", - "LessGreater;": "\u2276", - "LessLess;": "\u2AA1", - "LessSlantEqual;": "\u2A7D", - "LessTilde;": "\u2272", - "Lfr;": "\uD835\uDD0F", - "Ll;": "\u22D8", - "Lleftarrow;": "\u21DA", - "Lmidot;": "\u013F", - "LongLeftArrow;": "\u27F5", - "LongLeftRightArrow;": "\u27F7", - "LongRightArrow;": "\u27F6", - "Longleftarrow;": "\u27F8", - "Longleftrightarrow;": "\u27FA", - "Longrightarrow;": "\u27F9", - "Lopf;": "\uD835\uDD43", - "LowerLeftArrow;": "\u2199", - "LowerRightArrow;": "\u2198", - "Lscr;": "\u2112", - "Lsh;": "\u21B0", - "Lstrok;": "\u0141", - "Lt;": "\u226A", - "Map;": "\u2905", - "Mcy;": "\u041C", - "MediumSpace;": "\u205F", - "Mellintrf;": "\u2133", - "Mfr;": "\uD835\uDD10", - "MinusPlus;": "\u2213", - "Mopf;": "\uD835\uDD44", - "Mscr;": "\u2133", - "Mu;": "\u039C", - "NJcy;": "\u040A", - "Nacute;": "\u0143", - "Ncaron;": "\u0147", - "Ncedil;": "\u0145", - "Ncy;": "\u041D", - "NegativeMediumSpace;": "\u200B", - "NegativeThickSpace;": "\u200B", - "NegativeThinSpace;": "\u200B", - "NegativeVeryThinSpace;": "\u200B", - "NestedGreaterGreater;": "\u226B", - "NestedLessLess;": "\u226A", - "NewLine;": "\u000A", - "Nfr;": "\uD835\uDD11", - "NoBreak;": "\u2060", - "NonBreakingSpace;": "\u00A0", - "Nopf;": "\u2115", - "Not;": "\u2AEC", - "NotCongruent;": "\u2262", - "NotCupCap;": "\u226D", - "NotDoubleVerticalBar;": "\u2226", - "NotElement;": "\u2209", - "NotEqual;": "\u2260", - "NotEqualTilde;": "\u2242\u0338", - "NotExists;": "\u2204", - "NotGreater;": "\u226F", - "NotGreaterEqual;": "\u2271", - "NotGreaterFullEqual;": "\u2267\u0338", - "NotGreaterGreater;": "\u226B\u0338", - "NotGreaterLess;": "\u2279", - "NotGreaterSlantEqual;": "\u2A7E\u0338", - "NotGreaterTilde;": "\u2275", - "NotHumpDownHump;": "\u224E\u0338", - "NotHumpEqual;": "\u224F\u0338", - "NotLeftTriangle;": "\u22EA", - "NotLeftTriangleBar;": "\u29CF\u0338", - "NotLeftTriangleEqual;": "\u22EC", - "NotLess;": "\u226E", - "NotLessEqual;": "\u2270", - "NotLessGreater;": "\u2278", - "NotLessLess;": "\u226A\u0338", - "NotLessSlantEqual;": "\u2A7D\u0338", - "NotLessTilde;": "\u2274", - "NotNestedGreaterGreater;": "\u2AA2\u0338", - "NotNestedLessLess;": "\u2AA1\u0338", - "NotPrecedes;": "\u2280", - "NotPrecedesEqual;": "\u2AAF\u0338", - "NotPrecedesSlantEqual;": "\u22E0", - "NotReverseElement;": "\u220C", - "NotRightTriangle;": "\u22EB", - "NotRightTriangleBar;": "\u29D0\u0338", - "NotRightTriangleEqual;": "\u22ED", - "NotSquareSubset;": "\u228F\u0338", - "NotSquareSubsetEqual;": "\u22E2", - "NotSquareSuperset;": "\u2290\u0338", - "NotSquareSupersetEqual;": "\u22E3", - "NotSubset;": "\u2282\u20D2", - "NotSubsetEqual;": "\u2288", - "NotSucceeds;": "\u2281", - "NotSucceedsEqual;": "\u2AB0\u0338", - "NotSucceedsSlantEqual;": "\u22E1", - "NotSucceedsTilde;": "\u227F\u0338", - "NotSuperset;": "\u2283\u20D2", - "NotSupersetEqual;": "\u2289", - "NotTilde;": "\u2241", - "NotTildeEqual;": "\u2244", - "NotTildeFullEqual;": "\u2247", - "NotTildeTilde;": "\u2249", - "NotVerticalBar;": "\u2224", - "Nscr;": "\uD835\uDCA9", - "Ntilde": "\u00D1", - "Ntilde;": "\u00D1", - "Nu;": "\u039D", - "OElig;": "\u0152", - "Oacute": "\u00D3", - "Oacute;": "\u00D3", - "Ocirc": "\u00D4", - "Ocirc;": "\u00D4", - "Ocy;": "\u041E", - "Odblac;": "\u0150", - "Ofr;": "\uD835\uDD12", - "Ograve": "\u00D2", - "Ograve;": "\u00D2", - "Omacr;": "\u014C", - "Omega;": "\u03A9", - "Omicron;": "\u039F", - "Oopf;": "\uD835\uDD46", - "OpenCurlyDoubleQuote;": "\u201C", - "OpenCurlyQuote;": "\u2018", - "Or;": "\u2A54", - "Oscr;": "\uD835\uDCAA", - "Oslash": "\u00D8", - "Oslash;": "\u00D8", - "Otilde": "\u00D5", - "Otilde;": "\u00D5", - "Otimes;": "\u2A37", - "Ouml": "\u00D6", - "Ouml;": "\u00D6", - "OverBar;": "\u203E", - "OverBrace;": "\u23DE", - "OverBracket;": "\u23B4", - "OverParenthesis;": "\u23DC", - "PartialD;": "\u2202", - "Pcy;": "\u041F", - "Pfr;": "\uD835\uDD13", - "Phi;": "\u03A6", - "Pi;": "\u03A0", - "PlusMinus;": "\u00B1", - "Poincareplane;": "\u210C", - "Popf;": "\u2119", - "Pr;": "\u2ABB", - "Precedes;": "\u227A", - "PrecedesEqual;": "\u2AAF", - "PrecedesSlantEqual;": "\u227C", - "PrecedesTilde;": "\u227E", - "Prime;": "\u2033", - "Product;": "\u220F", - "Proportion;": "\u2237", - "Proportional;": "\u221D", - "Pscr;": "\uD835\uDCAB", - "Psi;": "\u03A8", - "QUOT": "\u0022", - "QUOT;": "\u0022", - "Qfr;": "\uD835\uDD14", - "Qopf;": "\u211A", - "Qscr;": "\uD835\uDCAC", - "RBarr;": "\u2910", - "REG": "\u00AE", - "REG;": "\u00AE", - "Racute;": "\u0154", - "Rang;": "\u27EB", - "Rarr;": "\u21A0", - "Rarrtl;": "\u2916", - "Rcaron;": "\u0158", - "Rcedil;": "\u0156", - "Rcy;": "\u0420", - "Re;": "\u211C", - "ReverseElement;": "\u220B", - "ReverseEquilibrium;": "\u21CB", - "ReverseUpEquilibrium;": "\u296F", - "Rfr;": "\u211C", - "Rho;": "\u03A1", - "RightAngleBracket;": "\u27E9", - "RightArrow;": "\u2192", - "RightArrowBar;": "\u21E5", - "RightArrowLeftArrow;": "\u21C4", - "RightCeiling;": "\u2309", - "RightDoubleBracket;": "\u27E7", - "RightDownTeeVector;": "\u295D", - "RightDownVector;": "\u21C2", - "RightDownVectorBar;": "\u2955", - "RightFloor;": "\u230B", - "RightTee;": "\u22A2", - "RightTeeArrow;": "\u21A6", - "RightTeeVector;": "\u295B", - "RightTriangle;": "\u22B3", - "RightTriangleBar;": "\u29D0", - "RightTriangleEqual;": "\u22B5", - "RightUpDownVector;": "\u294F", - "RightUpTeeVector;": "\u295C", - "RightUpVector;": "\u21BE", - "RightUpVectorBar;": "\u2954", - "RightVector;": "\u21C0", - "RightVectorBar;": "\u2953", - "Rightarrow;": "\u21D2", - "Ropf;": "\u211D", - "RoundImplies;": "\u2970", - "Rrightarrow;": "\u21DB", - "Rscr;": "\u211B", - "Rsh;": "\u21B1", - "RuleDelayed;": "\u29F4", - "SHCHcy;": "\u0429", - "SHcy;": "\u0428", - "SOFTcy;": "\u042C", - "Sacute;": "\u015A", - "Sc;": "\u2ABC", - "Scaron;": "\u0160", - "Scedil;": "\u015E", - "Scirc;": "\u015C", - "Scy;": "\u0421", - "Sfr;": "\uD835\uDD16", - "ShortDownArrow;": "\u2193", - "ShortLeftArrow;": "\u2190", - "ShortRightArrow;": "\u2192", - "ShortUpArrow;": "\u2191", - "Sigma;": "\u03A3", - "SmallCircle;": "\u2218", - "Sopf;": "\uD835\uDD4A", - "Sqrt;": "\u221A", - "Square;": "\u25A1", - "SquareIntersection;": "\u2293", - "SquareSubset;": "\u228F", - "SquareSubsetEqual;": "\u2291", - "SquareSuperset;": "\u2290", - "SquareSupersetEqual;": "\u2292", - "SquareUnion;": "\u2294", - "Sscr;": "\uD835\uDCAE", - "Star;": "\u22C6", - "Sub;": "\u22D0", - "Subset;": "\u22D0", - "SubsetEqual;": "\u2286", - "Succeeds;": "\u227B", - "SucceedsEqual;": "\u2AB0", - "SucceedsSlantEqual;": "\u227D", - "SucceedsTilde;": "\u227F", - "SuchThat;": "\u220B", - "Sum;": "\u2211", - "Sup;": "\u22D1", - "Superset;": "\u2283", - "SupersetEqual;": "\u2287", - "Supset;": "\u22D1", - "THORN": "\u00DE", - "THORN;": "\u00DE", - "TRADE;": "\u2122", - "TSHcy;": "\u040B", - "TScy;": "\u0426", - "Tab;": "\u0009", - "Tau;": "\u03A4", - "Tcaron;": "\u0164", - "Tcedil;": "\u0162", - "Tcy;": "\u0422", - "Tfr;": "\uD835\uDD17", - "Therefore;": "\u2234", - "Theta;": "\u0398", - "ThickSpace;": "\u205F\u200A", - "ThinSpace;": "\u2009", - "Tilde;": "\u223C", - "TildeEqual;": "\u2243", - "TildeFullEqual;": "\u2245", - "TildeTilde;": "\u2248", - "Topf;": "\uD835\uDD4B", - "TripleDot;": "\u20DB", - "Tscr;": "\uD835\uDCAF", - "Tstrok;": "\u0166", - "Uacute": "\u00DA", - "Uacute;": "\u00DA", - "Uarr;": "\u219F", - "Uarrocir;": "\u2949", - "Ubrcy;": "\u040E", - "Ubreve;": "\u016C", - "Ucirc": "\u00DB", - "Ucirc;": "\u00DB", - "Ucy;": "\u0423", - "Udblac;": "\u0170", - "Ufr;": "\uD835\uDD18", - "Ugrave": "\u00D9", - "Ugrave;": "\u00D9", - "Umacr;": "\u016A", - "UnderBar;": "\u005F", - "UnderBrace;": "\u23DF", - "UnderBracket;": "\u23B5", - "UnderParenthesis;": "\u23DD", - "Union;": "\u22C3", - "UnionPlus;": "\u228E", - "Uogon;": "\u0172", - "Uopf;": "\uD835\uDD4C", - "UpArrow;": "\u2191", - "UpArrowBar;": "\u2912", - "UpArrowDownArrow;": "\u21C5", - "UpDownArrow;": "\u2195", - "UpEquilibrium;": "\u296E", - "UpTee;": "\u22A5", - "UpTeeArrow;": "\u21A5", - "Uparrow;": "\u21D1", - "Updownarrow;": "\u21D5", - "UpperLeftArrow;": "\u2196", - "UpperRightArrow;": "\u2197", - "Upsi;": "\u03D2", - "Upsilon;": "\u03A5", - "Uring;": "\u016E", - "Uscr;": "\uD835\uDCB0", - "Utilde;": "\u0168", - "Uuml": "\u00DC", - "Uuml;": "\u00DC", - "VDash;": "\u22AB", - "Vbar;": "\u2AEB", - "Vcy;": "\u0412", - "Vdash;": "\u22A9", - "Vdashl;": "\u2AE6", - "Vee;": "\u22C1", - "Verbar;": "\u2016", - "Vert;": "\u2016", - "VerticalBar;": "\u2223", - "VerticalLine;": "\u007C", - "VerticalSeparator;": "\u2758", - "VerticalTilde;": "\u2240", - "VeryThinSpace;": "\u200A", - "Vfr;": "\uD835\uDD19", - "Vopf;": "\uD835\uDD4D", - "Vscr;": "\uD835\uDCB1", - "Vvdash;": "\u22AA", - "Wcirc;": "\u0174", - "Wedge;": "\u22C0", - "Wfr;": "\uD835\uDD1A", - "Wopf;": "\uD835\uDD4E", - "Wscr;": "\uD835\uDCB2", - "Xfr;": "\uD835\uDD1B", - "Xi;": "\u039E", - "Xopf;": "\uD835\uDD4F", - "Xscr;": "\uD835\uDCB3", - "YAcy;": "\u042F", - "YIcy;": "\u0407", - "YUcy;": "\u042E", - "Yacute": "\u00DD", - "Yacute;": "\u00DD", - "Ycirc;": "\u0176", - "Ycy;": "\u042B", - "Yfr;": "\uD835\uDD1C", - "Yopf;": "\uD835\uDD50", - "Yscr;": "\uD835\uDCB4", - "Yuml;": "\u0178", - "ZHcy;": "\u0416", - "Zacute;": "\u0179", - "Zcaron;": "\u017D", - "Zcy;": "\u0417", - "Zdot;": "\u017B", - "ZeroWidthSpace;": "\u200B", - "Zeta;": "\u0396", - "Zfr;": "\u2128", - "Zopf;": "\u2124", - "Zscr;": "\uD835\uDCB5", - "aacute": "\u00E1", - "aacute;": "\u00E1", - "abreve;": "\u0103", - "ac;": "\u223E", - "acE;": "\u223E\u0333", - "acd;": "\u223F", - "acirc": "\u00E2", - "acirc;": "\u00E2", - "acute": "\u00B4", - "acute;": "\u00B4", - "acy;": "\u0430", - "aelig": "\u00E6", - "aelig;": "\u00E6", - "af;": "\u2061", - "afr;": "\uD835\uDD1E", - "agrave": "\u00E0", - "agrave;": "\u00E0", - "alefsym;": "\u2135", - "aleph;": "\u2135", - "alpha;": "\u03B1", - "amacr;": "\u0101", - "amalg;": "\u2A3F", - "amp": "\u0026", - "amp;": "\u0026", - "and;": "\u2227", - "andand;": "\u2A55", - "andd;": "\u2A5C", - "andslope;": "\u2A58", - "andv;": "\u2A5A", - "ang;": "\u2220", - "ange;": "\u29A4", - "angle;": "\u2220", - "angmsd;": "\u2221", - "angmsdaa;": "\u29A8", - "angmsdab;": "\u29A9", - "angmsdac;": "\u29AA", - "angmsdad;": "\u29AB", - "angmsdae;": "\u29AC", - "angmsdaf;": "\u29AD", - "angmsdag;": "\u29AE", - "angmsdah;": "\u29AF", - "angrt;": "\u221F", - "angrtvb;": "\u22BE", - "angrtvbd;": "\u299D", - "angsph;": "\u2222", - "angst;": "\u00C5", - "angzarr;": "\u237C", - "aogon;": "\u0105", - "aopf;": "\uD835\uDD52", - "ap;": "\u2248", - "apE;": "\u2A70", - "apacir;": "\u2A6F", - "ape;": "\u224A", - "apid;": "\u224B", - "apos;": "\u0027", - "approx;": "\u2248", - "approxeq;": "\u224A", - "aring": "\u00E5", - "aring;": "\u00E5", - "ascr;": "\uD835\uDCB6", - "ast;": "\u002A", - "asymp;": "\u2248", - "asympeq;": "\u224D", - "atilde": "\u00E3", - "atilde;": "\u00E3", - "auml": "\u00E4", - "auml;": "\u00E4", - "awconint;": "\u2233", - "awint;": "\u2A11", - "bNot;": "\u2AED", - "backcong;": "\u224C", - "backepsilon;": "\u03F6", - "backprime;": "\u2035", - "backsim;": "\u223D", - "backsimeq;": "\u22CD", - "barvee;": "\u22BD", - "barwed;": "\u2305", - "barwedge;": "\u2305", - "bbrk;": "\u23B5", - "bbrktbrk;": "\u23B6", - "bcong;": "\u224C", - "bcy;": "\u0431", - "bdquo;": "\u201E", - "becaus;": "\u2235", - "because;": "\u2235", - "bemptyv;": "\u29B0", - "bepsi;": "\u03F6", - "bernou;": "\u212C", - "beta;": "\u03B2", - "beth;": "\u2136", - "between;": "\u226C", - "bfr;": "\uD835\uDD1F", - "bigcap;": "\u22C2", - "bigcirc;": "\u25EF", - "bigcup;": "\u22C3", - "bigodot;": "\u2A00", - "bigoplus;": "\u2A01", - "bigotimes;": "\u2A02", - "bigsqcup;": "\u2A06", - "bigstar;": "\u2605", - "bigtriangledown;": "\u25BD", - "bigtriangleup;": "\u25B3", - "biguplus;": "\u2A04", - "bigvee;": "\u22C1", - "bigwedge;": "\u22C0", - "bkarow;": "\u290D", - "blacklozenge;": "\u29EB", - "blacksquare;": "\u25AA", - "blacktriangle;": "\u25B4", - "blacktriangledown;": "\u25BE", - "blacktriangleleft;": "\u25C2", - "blacktriangleright;": "\u25B8", - "blank;": "\u2423", - "blk12;": "\u2592", - "blk14;": "\u2591", - "blk34;": "\u2593", - "block;": "\u2588", - "bne;": "\u003D\u20E5", - "bnequiv;": "\u2261\u20E5", - "bnot;": "\u2310", - "bopf;": "\uD835\uDD53", - "bot;": "\u22A5", - "bottom;": "\u22A5", - "bowtie;": "\u22C8", - "boxDL;": "\u2557", - "boxDR;": "\u2554", - "boxDl;": "\u2556", - "boxDr;": "\u2553", - "boxH;": "\u2550", - "boxHD;": "\u2566", - "boxHU;": "\u2569", - "boxHd;": "\u2564", - "boxHu;": "\u2567", - "boxUL;": "\u255D", - "boxUR;": "\u255A", - "boxUl;": "\u255C", - "boxUr;": "\u2559", - "boxV;": "\u2551", - "boxVH;": "\u256C", - "boxVL;": "\u2563", - "boxVR;": "\u2560", - "boxVh;": "\u256B", - "boxVl;": "\u2562", - "boxVr;": "\u255F", - "boxbox;": "\u29C9", - "boxdL;": "\u2555", - "boxdR;": "\u2552", - "boxdl;": "\u2510", - "boxdr;": "\u250C", - "boxh;": "\u2500", - "boxhD;": "\u2565", - "boxhU;": "\u2568", - "boxhd;": "\u252C", - "boxhu;": "\u2534", - "boxminus;": "\u229F", - "boxplus;": "\u229E", - "boxtimes;": "\u22A0", - "boxuL;": "\u255B", - "boxuR;": "\u2558", - "boxul;": "\u2518", - "boxur;": "\u2514", - "boxv;": "\u2502", - "boxvH;": "\u256A", - "boxvL;": "\u2561", - "boxvR;": "\u255E", - "boxvh;": "\u253C", - "boxvl;": "\u2524", - "boxvr;": "\u251C", - "bprime;": "\u2035", - "breve;": "\u02D8", - "brvbar": "\u00A6", - "brvbar;": "\u00A6", - "bscr;": "\uD835\uDCB7", - "bsemi;": "\u204F", - "bsim;": "\u223D", - "bsime;": "\u22CD", - "bsol;": "\u005C", - "bsolb;": "\u29C5", - "bsolhsub;": "\u27C8", - "bull;": "\u2022", - "bullet;": "\u2022", - "bump;": "\u224E", - "bumpE;": "\u2AAE", - "bumpe;": "\u224F", - "bumpeq;": "\u224F", - "cacute;": "\u0107", - "cap;": "\u2229", - "capand;": "\u2A44", - "capbrcup;": "\u2A49", - "capcap;": "\u2A4B", - "capcup;": "\u2A47", - "capdot;": "\u2A40", - "caps;": "\u2229\uFE00", - "caret;": "\u2041", - "caron;": "\u02C7", - "ccaps;": "\u2A4D", - "ccaron;": "\u010D", - "ccedil": "\u00E7", - "ccedil;": "\u00E7", - "ccirc;": "\u0109", - "ccups;": "\u2A4C", - "ccupssm;": "\u2A50", - "cdot;": "\u010B", - "cedil": "\u00B8", - "cedil;": "\u00B8", - "cemptyv;": "\u29B2", - "cent": "\u00A2", - "cent;": "\u00A2", - "centerdot;": "\u00B7", - "cfr;": "\uD835\uDD20", - "chcy;": "\u0447", - "check;": "\u2713", - "checkmark;": "\u2713", - "chi;": "\u03C7", - "cir;": "\u25CB", - "cirE;": "\u29C3", - "circ;": "\u02C6", - "circeq;": "\u2257", - "circlearrowleft;": "\u21BA", - "circlearrowright;": "\u21BB", - "circledR;": "\u00AE", - "circledS;": "\u24C8", - "circledast;": "\u229B", - "circledcirc;": "\u229A", - "circleddash;": "\u229D", - "cire;": "\u2257", - "cirfnint;": "\u2A10", - "cirmid;": "\u2AEF", - "cirscir;": "\u29C2", - "clubs;": "\u2663", - "clubsuit;": "\u2663", - "colon;": "\u003A", - "colone;": "\u2254", - "coloneq;": "\u2254", - "comma;": "\u002C", - "commat;": "\u0040", - "comp;": "\u2201", - "compfn;": "\u2218", - "complement;": "\u2201", - "complexes;": "\u2102", - "cong;": "\u2245", - "congdot;": "\u2A6D", - "conint;": "\u222E", - "copf;": "\uD835\uDD54", - "coprod;": "\u2210", - "copy": "\u00A9", - "copy;": "\u00A9", - "copysr;": "\u2117", - "crarr;": "\u21B5", - "cross;": "\u2717", - "cscr;": "\uD835\uDCB8", - "csub;": "\u2ACF", - "csube;": "\u2AD1", - "csup;": "\u2AD0", - "csupe;": "\u2AD2", - "ctdot;": "\u22EF", - "cudarrl;": "\u2938", - "cudarrr;": "\u2935", - "cuepr;": "\u22DE", - "cuesc;": "\u22DF", - "cularr;": "\u21B6", - "cularrp;": "\u293D", - "cup;": "\u222A", - "cupbrcap;": "\u2A48", - "cupcap;": "\u2A46", - "cupcup;": "\u2A4A", - "cupdot;": "\u228D", - "cupor;": "\u2A45", - "cups;": "\u222A\uFE00", - "curarr;": "\u21B7", - "curarrm;": "\u293C", - "curlyeqprec;": "\u22DE", - "curlyeqsucc;": "\u22DF", - "curlyvee;": "\u22CE", - "curlywedge;": "\u22CF", - "curren": "\u00A4", - "curren;": "\u00A4", - "curvearrowleft;": "\u21B6", - "curvearrowright;": "\u21B7", - "cuvee;": "\u22CE", - "cuwed;": "\u22CF", - "cwconint;": "\u2232", - "cwint;": "\u2231", - "cylcty;": "\u232D", - "dArr;": "\u21D3", - "dHar;": "\u2965", - "dagger;": "\u2020", - "daleth;": "\u2138", - "darr;": "\u2193", - "dash;": "\u2010", - "dashv;": "\u22A3", - "dbkarow;": "\u290F", - "dblac;": "\u02DD", - "dcaron;": "\u010F", - "dcy;": "\u0434", - "dd;": "\u2146", - "ddagger;": "\u2021", - "ddarr;": "\u21CA", - "ddotseq;": "\u2A77", - "deg": "\u00B0", - "deg;": "\u00B0", - "delta;": "\u03B4", - "demptyv;": "\u29B1", - "dfisht;": "\u297F", - "dfr;": "\uD835\uDD21", - "dharl;": "\u21C3", - "dharr;": "\u21C2", - "diam;": "\u22C4", - "diamond;": "\u22C4", - "diamondsuit;": "\u2666", - "diams;": "\u2666", - "die;": "\u00A8", - "digamma;": "\u03DD", - "disin;": "\u22F2", - "div;": "\u00F7", - "divide": "\u00F7", - "divide;": "\u00F7", - "divideontimes;": "\u22C7", - "divonx;": "\u22C7", - "djcy;": "\u0452", - "dlcorn;": "\u231E", - "dlcrop;": "\u230D", - "dollar;": "\u0024", - "dopf;": "\uD835\uDD55", - "dot;": "\u02D9", - "doteq;": "\u2250", - "doteqdot;": "\u2251", - "dotminus;": "\u2238", - "dotplus;": "\u2214", - "dotsquare;": "\u22A1", - "doublebarwedge;": "\u2306", - "downarrow;": "\u2193", - "downdownarrows;": "\u21CA", - "downharpoonleft;": "\u21C3", - "downharpoonright;": "\u21C2", - "drbkarow;": "\u2910", - "drcorn;": "\u231F", - "drcrop;": "\u230C", - "dscr;": "\uD835\uDCB9", - "dscy;": "\u0455", - "dsol;": "\u29F6", - "dstrok;": "\u0111", - "dtdot;": "\u22F1", - "dtri;": "\u25BF", - "dtrif;": "\u25BE", - "duarr;": "\u21F5", - "duhar;": "\u296F", - "dwangle;": "\u29A6", - "dzcy;": "\u045F", - "dzigrarr;": "\u27FF", - "eDDot;": "\u2A77", - "eDot;": "\u2251", - "eacute": "\u00E9", - "eacute;": "\u00E9", - "easter;": "\u2A6E", - "ecaron;": "\u011B", - "ecir;": "\u2256", - "ecirc": "\u00EA", - "ecirc;": "\u00EA", - "ecolon;": "\u2255", - "ecy;": "\u044D", - "edot;": "\u0117", - "ee;": "\u2147", - "efDot;": "\u2252", - "efr;": "\uD835\uDD22", - "eg;": "\u2A9A", - "egrave": "\u00E8", - "egrave;": "\u00E8", - "egs;": "\u2A96", - "egsdot;": "\u2A98", - "el;": "\u2A99", - "elinters;": "\u23E7", - "ell;": "\u2113", - "els;": "\u2A95", - "elsdot;": "\u2A97", - "emacr;": "\u0113", - "empty;": "\u2205", - "emptyset;": "\u2205", - "emptyv;": "\u2205", - "emsp13;": "\u2004", - "emsp14;": "\u2005", - "emsp;": "\u2003", - "eng;": "\u014B", - "ensp;": "\u2002", - "eogon;": "\u0119", - "eopf;": "\uD835\uDD56", - "epar;": "\u22D5", - "eparsl;": "\u29E3", - "eplus;": "\u2A71", - "epsi;": "\u03B5", - "epsilon;": "\u03B5", - "epsiv;": "\u03F5", - "eqcirc;": "\u2256", - "eqcolon;": "\u2255", - "eqsim;": "\u2242", - "eqslantgtr;": "\u2A96", - "eqslantless;": "\u2A95", - "equals;": "\u003D", - "equest;": "\u225F", - "equiv;": "\u2261", - "equivDD;": "\u2A78", - "eqvparsl;": "\u29E5", - "erDot;": "\u2253", - "erarr;": "\u2971", - "escr;": "\u212F", - "esdot;": "\u2250", - "esim;": "\u2242", - "eta;": "\u03B7", - "eth": "\u00F0", - "eth;": "\u00F0", - "euml": "\u00EB", - "euml;": "\u00EB", - "euro;": "\u20AC", - "excl;": "\u0021", - "exist;": "\u2203", - "expectation;": "\u2130", - "exponentiale;": "\u2147", - "fallingdotseq;": "\u2252", - "fcy;": "\u0444", - "female;": "\u2640", - "ffilig;": "\uFB03", - "fflig;": "\uFB00", - "ffllig;": "\uFB04", - "ffr;": "\uD835\uDD23", - "filig;": "\uFB01", - "fjlig;": "\u0066\u006A", - "flat;": "\u266D", - "fllig;": "\uFB02", - "fltns;": "\u25B1", - "fnof;": "\u0192", - "fopf;": "\uD835\uDD57", - "forall;": "\u2200", - "fork;": "\u22D4", - "forkv;": "\u2AD9", - "fpartint;": "\u2A0D", - "frac12": "\u00BD", - "frac12;": "\u00BD", - "frac13;": "\u2153", - "frac14": "\u00BC", - "frac14;": "\u00BC", - "frac15;": "\u2155", - "frac16;": "\u2159", - "frac18;": "\u215B", - "frac23;": "\u2154", - "frac25;": "\u2156", - "frac34": "\u00BE", - "frac34;": "\u00BE", - "frac35;": "\u2157", - "frac38;": "\u215C", - "frac45;": "\u2158", - "frac56;": "\u215A", - "frac58;": "\u215D", - "frac78;": "\u215E", - "frasl;": "\u2044", - "frown;": "\u2322", - "fscr;": "\uD835\uDCBB", - "gE;": "\u2267", - "gEl;": "\u2A8C", - "gacute;": "\u01F5", - "gamma;": "\u03B3", - "gammad;": "\u03DD", - "gap;": "\u2A86", - "gbreve;": "\u011F", - "gcirc;": "\u011D", - "gcy;": "\u0433", - "gdot;": "\u0121", - "ge;": "\u2265", - "gel;": "\u22DB", - "geq;": "\u2265", - "geqq;": "\u2267", - "geqslant;": "\u2A7E", - "ges;": "\u2A7E", - "gescc;": "\u2AA9", - "gesdot;": "\u2A80", - "gesdoto;": "\u2A82", - "gesdotol;": "\u2A84", - "gesl;": "\u22DB\uFE00", - "gesles;": "\u2A94", - "gfr;": "\uD835\uDD24", - "gg;": "\u226B", - "ggg;": "\u22D9", - "gimel;": "\u2137", - "gjcy;": "\u0453", - "gl;": "\u2277", - "glE;": "\u2A92", - "gla;": "\u2AA5", - "glj;": "\u2AA4", - "gnE;": "\u2269", - "gnap;": "\u2A8A", - "gnapprox;": "\u2A8A", - "gne;": "\u2A88", - "gneq;": "\u2A88", - "gneqq;": "\u2269", - "gnsim;": "\u22E7", - "gopf;": "\uD835\uDD58", - "grave;": "\u0060", - "gscr;": "\u210A", - "gsim;": "\u2273", - "gsime;": "\u2A8E", - "gsiml;": "\u2A90", - "gt": "\u003E", - "gt;": "\u003E", - "gtcc;": "\u2AA7", - "gtcir;": "\u2A7A", - "gtdot;": "\u22D7", - "gtlPar;": "\u2995", - "gtquest;": "\u2A7C", - "gtrapprox;": "\u2A86", - "gtrarr;": "\u2978", - "gtrdot;": "\u22D7", - "gtreqless;": "\u22DB", - "gtreqqless;": "\u2A8C", - "gtrless;": "\u2277", - "gtrsim;": "\u2273", - "gvertneqq;": "\u2269\uFE00", - "gvnE;": "\u2269\uFE00", - "hArr;": "\u21D4", - "hairsp;": "\u200A", - "half;": "\u00BD", - "hamilt;": "\u210B", - "hardcy;": "\u044A", - "harr;": "\u2194", - "harrcir;": "\u2948", - "harrw;": "\u21AD", - "hbar;": "\u210F", - "hcirc;": "\u0125", - "hearts;": "\u2665", - "heartsuit;": "\u2665", - "hellip;": "\u2026", - "hercon;": "\u22B9", - "hfr;": "\uD835\uDD25", - "hksearow;": "\u2925", - "hkswarow;": "\u2926", - "hoarr;": "\u21FF", - "homtht;": "\u223B", - "hookleftarrow;": "\u21A9", - "hookrightarrow;": "\u21AA", - "hopf;": "\uD835\uDD59", - "horbar;": "\u2015", - "hscr;": "\uD835\uDCBD", - "hslash;": "\u210F", - "hstrok;": "\u0127", - "hybull;": "\u2043", - "hyphen;": "\u2010", - "iacute": "\u00ED", - "iacute;": "\u00ED", - "ic;": "\u2063", - "icirc": "\u00EE", - "icirc;": "\u00EE", - "icy;": "\u0438", - "iecy;": "\u0435", - "iexcl": "\u00A1", - "iexcl;": "\u00A1", - "iff;": "\u21D4", - "ifr;": "\uD835\uDD26", - "igrave": "\u00EC", - "igrave;": "\u00EC", - "ii;": "\u2148", - "iiiint;": "\u2A0C", - "iiint;": "\u222D", - "iinfin;": "\u29DC", - "iiota;": "\u2129", - "ijlig;": "\u0133", - "imacr;": "\u012B", - "image;": "\u2111", - "imagline;": "\u2110", - "imagpart;": "\u2111", - "imath;": "\u0131", - "imof;": "\u22B7", - "imped;": "\u01B5", - "in;": "\u2208", - "incare;": "\u2105", - "infin;": "\u221E", - "infintie;": "\u29DD", - "inodot;": "\u0131", - "int;": "\u222B", - "intcal;": "\u22BA", - "integers;": "\u2124", - "intercal;": "\u22BA", - "intlarhk;": "\u2A17", - "intprod;": "\u2A3C", - "iocy;": "\u0451", - "iogon;": "\u012F", - "iopf;": "\uD835\uDD5A", - "iota;": "\u03B9", - "iprod;": "\u2A3C", - "iquest": "\u00BF", - "iquest;": "\u00BF", - "iscr;": "\uD835\uDCBE", - "isin;": "\u2208", - "isinE;": "\u22F9", - "isindot;": "\u22F5", - "isins;": "\u22F4", - "isinsv;": "\u22F3", - "isinv;": "\u2208", - "it;": "\u2062", - "itilde;": "\u0129", - "iukcy;": "\u0456", - "iuml": "\u00EF", - "iuml;": "\u00EF", - "jcirc;": "\u0135", - "jcy;": "\u0439", - "jfr;": "\uD835\uDD27", - "jmath;": "\u0237", - "jopf;": "\uD835\uDD5B", - "jscr;": "\uD835\uDCBF", - "jsercy;": "\u0458", - "jukcy;": "\u0454", - "kappa;": "\u03BA", - "kappav;": "\u03F0", - "kcedil;": "\u0137", - "kcy;": "\u043A", - "kfr;": "\uD835\uDD28", - "kgreen;": "\u0138", - "khcy;": "\u0445", - "kjcy;": "\u045C", - "kopf;": "\uD835\uDD5C", - "kscr;": "\uD835\uDCC0", - "lAarr;": "\u21DA", - "lArr;": "\u21D0", - "lAtail;": "\u291B", - "lBarr;": "\u290E", - "lE;": "\u2266", - "lEg;": "\u2A8B", - "lHar;": "\u2962", - "lacute;": "\u013A", - "laemptyv;": "\u29B4", - "lagran;": "\u2112", - "lambda;": "\u03BB", - "lang;": "\u27E8", - "langd;": "\u2991", - "langle;": "\u27E8", - "lap;": "\u2A85", - "laquo": "\u00AB", - "laquo;": "\u00AB", - "larr;": "\u2190", - "larrb;": "\u21E4", - "larrbfs;": "\u291F", - "larrfs;": "\u291D", - "larrhk;": "\u21A9", - "larrlp;": "\u21AB", - "larrpl;": "\u2939", - "larrsim;": "\u2973", - "larrtl;": "\u21A2", - "lat;": "\u2AAB", - "latail;": "\u2919", - "late;": "\u2AAD", - "lates;": "\u2AAD\uFE00", - "lbarr;": "\u290C", - "lbbrk;": "\u2772", - "lbrace;": "\u007B", - "lbrack;": "\u005B", - "lbrke;": "\u298B", - "lbrksld;": "\u298F", - "lbrkslu;": "\u298D", - "lcaron;": "\u013E", - "lcedil;": "\u013C", - "lceil;": "\u2308", - "lcub;": "\u007B", - "lcy;": "\u043B", - "ldca;": "\u2936", - "ldquo;": "\u201C", - "ldquor;": "\u201E", - "ldrdhar;": "\u2967", - "ldrushar;": "\u294B", - "ldsh;": "\u21B2", - "le;": "\u2264", - "leftarrow;": "\u2190", - "leftarrowtail;": "\u21A2", - "leftharpoondown;": "\u21BD", - "leftharpoonup;": "\u21BC", - "leftleftarrows;": "\u21C7", - "leftrightarrow;": "\u2194", - "leftrightarrows;": "\u21C6", - "leftrightharpoons;": "\u21CB", - "leftrightsquigarrow;": "\u21AD", - "leftthreetimes;": "\u22CB", - "leg;": "\u22DA", - "leq;": "\u2264", - "leqq;": "\u2266", - "leqslant;": "\u2A7D", - "les;": "\u2A7D", - "lescc;": "\u2AA8", - "lesdot;": "\u2A7F", - "lesdoto;": "\u2A81", - "lesdotor;": "\u2A83", - "lesg;": "\u22DA\uFE00", - "lesges;": "\u2A93", - "lessapprox;": "\u2A85", - "lessdot;": "\u22D6", - "lesseqgtr;": "\u22DA", - "lesseqqgtr;": "\u2A8B", - "lessgtr;": "\u2276", - "lesssim;": "\u2272", - "lfisht;": "\u297C", - "lfloor;": "\u230A", - "lfr;": "\uD835\uDD29", - "lg;": "\u2276", - "lgE;": "\u2A91", - "lhard;": "\u21BD", - "lharu;": "\u21BC", - "lharul;": "\u296A", - "lhblk;": "\u2584", - "ljcy;": "\u0459", - "ll;": "\u226A", - "llarr;": "\u21C7", - "llcorner;": "\u231E", - "llhard;": "\u296B", - "lltri;": "\u25FA", - "lmidot;": "\u0140", - "lmoust;": "\u23B0", - "lmoustache;": "\u23B0", - "lnE;": "\u2268", - "lnap;": "\u2A89", - "lnapprox;": "\u2A89", - "lne;": "\u2A87", - "lneq;": "\u2A87", - "lneqq;": "\u2268", - "lnsim;": "\u22E6", - "loang;": "\u27EC", - "loarr;": "\u21FD", - "lobrk;": "\u27E6", - "longleftarrow;": "\u27F5", - "longleftrightarrow;": "\u27F7", - "longmapsto;": "\u27FC", - "longrightarrow;": "\u27F6", - "looparrowleft;": "\u21AB", - "looparrowright;": "\u21AC", - "lopar;": "\u2985", - "lopf;": "\uD835\uDD5D", - "loplus;": "\u2A2D", - "lotimes;": "\u2A34", - "lowast;": "\u2217", - "lowbar;": "\u005F", - "loz;": "\u25CA", - "lozenge;": "\u25CA", - "lozf;": "\u29EB", - "lpar;": "\u0028", - "lparlt;": "\u2993", - "lrarr;": "\u21C6", - "lrcorner;": "\u231F", - "lrhar;": "\u21CB", - "lrhard;": "\u296D", - "lrm;": "\u200E", - "lrtri;": "\u22BF", - "lsaquo;": "\u2039", - "lscr;": "\uD835\uDCC1", - "lsh;": "\u21B0", - "lsim;": "\u2272", - "lsime;": "\u2A8D", - "lsimg;": "\u2A8F", - "lsqb;": "\u005B", - "lsquo;": "\u2018", - "lsquor;": "\u201A", - "lstrok;": "\u0142", - "lt": "\u003C", - "lt;": "\u003C", - "ltcc;": "\u2AA6", - "ltcir;": "\u2A79", - "ltdot;": "\u22D6", - "lthree;": "\u22CB", - "ltimes;": "\u22C9", - "ltlarr;": "\u2976", - "ltquest;": "\u2A7B", - "ltrPar;": "\u2996", - "ltri;": "\u25C3", - "ltrie;": "\u22B4", - "ltrif;": "\u25C2", - "lurdshar;": "\u294A", - "luruhar;": "\u2966", - "lvertneqq;": "\u2268\uFE00", - "lvnE;": "\u2268\uFE00", - "mDDot;": "\u223A", - "macr": "\u00AF", - "macr;": "\u00AF", - "male;": "\u2642", - "malt;": "\u2720", - "maltese;": "\u2720", - "map;": "\u21A6", - "mapsto;": "\u21A6", - "mapstodown;": "\u21A7", - "mapstoleft;": "\u21A4", - "mapstoup;": "\u21A5", - "marker;": "\u25AE", - "mcomma;": "\u2A29", - "mcy;": "\u043C", - "mdash;": "\u2014", - "measuredangle;": "\u2221", - "mfr;": "\uD835\uDD2A", - "mho;": "\u2127", - "micro": "\u00B5", - "micro;": "\u00B5", - "mid;": "\u2223", - "midast;": "\u002A", - "midcir;": "\u2AF0", - "middot": "\u00B7", - "middot;": "\u00B7", - "minus;": "\u2212", - "minusb;": "\u229F", - "minusd;": "\u2238", - "minusdu;": "\u2A2A", - "mlcp;": "\u2ADB", - "mldr;": "\u2026", - "mnplus;": "\u2213", - "models;": "\u22A7", - "mopf;": "\uD835\uDD5E", - "mp;": "\u2213", - "mscr;": "\uD835\uDCC2", - "mstpos;": "\u223E", - "mu;": "\u03BC", - "multimap;": "\u22B8", - "mumap;": "\u22B8", - "nGg;": "\u22D9\u0338", - "nGt;": "\u226B\u20D2", - "nGtv;": "\u226B\u0338", - "nLeftarrow;": "\u21CD", - "nLeftrightarrow;": "\u21CE", - "nLl;": "\u22D8\u0338", - "nLt;": "\u226A\u20D2", - "nLtv;": "\u226A\u0338", - "nRightarrow;": "\u21CF", - "nVDash;": "\u22AF", - "nVdash;": "\u22AE", - "nabla;": "\u2207", - "nacute;": "\u0144", - "nang;": "\u2220\u20D2", - "nap;": "\u2249", - "napE;": "\u2A70\u0338", - "napid;": "\u224B\u0338", - "napos;": "\u0149", - "napprox;": "\u2249", - "natur;": "\u266E", - "natural;": "\u266E", - "naturals;": "\u2115", - "nbsp": "\u00A0", - "nbsp;": "\u00A0", - "nbump;": "\u224E\u0338", - "nbumpe;": "\u224F\u0338", - "ncap;": "\u2A43", - "ncaron;": "\u0148", - "ncedil;": "\u0146", - "ncong;": "\u2247", - "ncongdot;": "\u2A6D\u0338", - "ncup;": "\u2A42", - "ncy;": "\u043D", - "ndash;": "\u2013", - "ne;": "\u2260", - "neArr;": "\u21D7", - "nearhk;": "\u2924", - "nearr;": "\u2197", - "nearrow;": "\u2197", - "nedot;": "\u2250\u0338", - "nequiv;": "\u2262", - "nesear;": "\u2928", - "nesim;": "\u2242\u0338", - "nexist;": "\u2204", - "nexists;": "\u2204", - "nfr;": "\uD835\uDD2B", - "ngE;": "\u2267\u0338", - "nge;": "\u2271", - "ngeq;": "\u2271", - "ngeqq;": "\u2267\u0338", - "ngeqslant;": "\u2A7E\u0338", - "nges;": "\u2A7E\u0338", - "ngsim;": "\u2275", - "ngt;": "\u226F", - "ngtr;": "\u226F", - "nhArr;": "\u21CE", - "nharr;": "\u21AE", - "nhpar;": "\u2AF2", - "ni;": "\u220B", - "nis;": "\u22FC", - "nisd;": "\u22FA", - "niv;": "\u220B", - "njcy;": "\u045A", - "nlArr;": "\u21CD", - "nlE;": "\u2266\u0338", - "nlarr;": "\u219A", - "nldr;": "\u2025", - "nle;": "\u2270", - "nleftarrow;": "\u219A", - "nleftrightarrow;": "\u21AE", - "nleq;": "\u2270", - "nleqq;": "\u2266\u0338", - "nleqslant;": "\u2A7D\u0338", - "nles;": "\u2A7D\u0338", - "nless;": "\u226E", - "nlsim;": "\u2274", - "nlt;": "\u226E", - "nltri;": "\u22EA", - "nltrie;": "\u22EC", - "nmid;": "\u2224", - "nopf;": "\uD835\uDD5F", - "not": "\u00AC", - "not;": "\u00AC", - "notin;": "\u2209", - "notinE;": "\u22F9\u0338", - "notindot;": "\u22F5\u0338", - "notinva;": "\u2209", - "notinvb;": "\u22F7", - "notinvc;": "\u22F6", - "notni;": "\u220C", - "notniva;": "\u220C", - "notnivb;": "\u22FE", - "notnivc;": "\u22FD", - "npar;": "\u2226", - "nparallel;": "\u2226", - "nparsl;": "\u2AFD\u20E5", - "npart;": "\u2202\u0338", - "npolint;": "\u2A14", - "npr;": "\u2280", - "nprcue;": "\u22E0", - "npre;": "\u2AAF\u0338", - "nprec;": "\u2280", - "npreceq;": "\u2AAF\u0338", - "nrArr;": "\u21CF", - "nrarr;": "\u219B", - "nrarrc;": "\u2933\u0338", - "nrarrw;": "\u219D\u0338", - "nrightarrow;": "\u219B", - "nrtri;": "\u22EB", - "nrtrie;": "\u22ED", - "nsc;": "\u2281", - "nsccue;": "\u22E1", - "nsce;": "\u2AB0\u0338", - "nscr;": "\uD835\uDCC3", - "nshortmid;": "\u2224", - "nshortparallel;": "\u2226", - "nsim;": "\u2241", - "nsime;": "\u2244", - "nsimeq;": "\u2244", - "nsmid;": "\u2224", - "nspar;": "\u2226", - "nsqsube;": "\u22E2", - "nsqsupe;": "\u22E3", - "nsub;": "\u2284", - "nsubE;": "\u2AC5\u0338", - "nsube;": "\u2288", - "nsubset;": "\u2282\u20D2", - "nsubseteq;": "\u2288", - "nsubseteqq;": "\u2AC5\u0338", - "nsucc;": "\u2281", - "nsucceq;": "\u2AB0\u0338", - "nsup;": "\u2285", - "nsupE;": "\u2AC6\u0338", - "nsupe;": "\u2289", - "nsupset;": "\u2283\u20D2", - "nsupseteq;": "\u2289", - "nsupseteqq;": "\u2AC6\u0338", - "ntgl;": "\u2279", - "ntilde": "\u00F1", - "ntilde;": "\u00F1", - "ntlg;": "\u2278", - "ntriangleleft;": "\u22EA", - "ntrianglelefteq;": "\u22EC", - "ntriangleright;": "\u22EB", - "ntrianglerighteq;": "\u22ED", - "nu;": "\u03BD", - "num;": "\u0023", - "numero;": "\u2116", - "numsp;": "\u2007", - "nvDash;": "\u22AD", - "nvHarr;": "\u2904", - "nvap;": "\u224D\u20D2", - "nvdash;": "\u22AC", - "nvge;": "\u2265\u20D2", - "nvgt;": "\u003E\u20D2", - "nvinfin;": "\u29DE", - "nvlArr;": "\u2902", - "nvle;": "\u2264\u20D2", - "nvlt;": "\u003C\u20D2", - "nvltrie;": "\u22B4\u20D2", - "nvrArr;": "\u2903", - "nvrtrie;": "\u22B5\u20D2", - "nvsim;": "\u223C\u20D2", - "nwArr;": "\u21D6", - "nwarhk;": "\u2923", - "nwarr;": "\u2196", - "nwarrow;": "\u2196", - "nwnear;": "\u2927", - "oS;": "\u24C8", - "oacute": "\u00F3", - "oacute;": "\u00F3", - "oast;": "\u229B", - "ocir;": "\u229A", - "ocirc": "\u00F4", - "ocirc;": "\u00F4", - "ocy;": "\u043E", - "odash;": "\u229D", - "odblac;": "\u0151", - "odiv;": "\u2A38", - "odot;": "\u2299", - "odsold;": "\u29BC", - "oelig;": "\u0153", - "ofcir;": "\u29BF", - "ofr;": "\uD835\uDD2C", - "ogon;": "\u02DB", - "ograve": "\u00F2", - "ograve;": "\u00F2", - "ogt;": "\u29C1", - "ohbar;": "\u29B5", - "ohm;": "\u03A9", - "oint;": "\u222E", - "olarr;": "\u21BA", - "olcir;": "\u29BE", - "olcross;": "\u29BB", - "oline;": "\u203E", - "olt;": "\u29C0", - "omacr;": "\u014D", - "omega;": "\u03C9", - "omicron;": "\u03BF", - "omid;": "\u29B6", - "ominus;": "\u2296", - "oopf;": "\uD835\uDD60", - "opar;": "\u29B7", - "operp;": "\u29B9", - "oplus;": "\u2295", - "or;": "\u2228", - "orarr;": "\u21BB", - "ord;": "\u2A5D", - "order;": "\u2134", - "orderof;": "\u2134", - "ordf": "\u00AA", - "ordf;": "\u00AA", - "ordm": "\u00BA", - "ordm;": "\u00BA", - "origof;": "\u22B6", - "oror;": "\u2A56", - "orslope;": "\u2A57", - "orv;": "\u2A5B", - "oscr;": "\u2134", - "oslash": "\u00F8", - "oslash;": "\u00F8", - "osol;": "\u2298", - "otilde": "\u00F5", - "otilde;": "\u00F5", - "otimes;": "\u2297", - "otimesas;": "\u2A36", - "ouml": "\u00F6", - "ouml;": "\u00F6", - "ovbar;": "\u233D", - "par;": "\u2225", - "para": "\u00B6", - "para;": "\u00B6", - "parallel;": "\u2225", - "parsim;": "\u2AF3", - "parsl;": "\u2AFD", - "part;": "\u2202", - "pcy;": "\u043F", - "percnt;": "\u0025", - "period;": "\u002E", - "permil;": "\u2030", - "perp;": "\u22A5", - "pertenk;": "\u2031", - "pfr;": "\uD835\uDD2D", - "phi;": "\u03C6", - "phiv;": "\u03D5", - "phmmat;": "\u2133", - "phone;": "\u260E", - "pi;": "\u03C0", - "pitchfork;": "\u22D4", - "piv;": "\u03D6", - "planck;": "\u210F", - "planckh;": "\u210E", - "plankv;": "\u210F", - "plus;": "\u002B", - "plusacir;": "\u2A23", - "plusb;": "\u229E", - "pluscir;": "\u2A22", - "plusdo;": "\u2214", - "plusdu;": "\u2A25", - "pluse;": "\u2A72", - "plusmn": "\u00B1", - "plusmn;": "\u00B1", - "plussim;": "\u2A26", - "plustwo;": "\u2A27", - "pm;": "\u00B1", - "pointint;": "\u2A15", - "popf;": "\uD835\uDD61", - "pound": "\u00A3", - "pound;": "\u00A3", - "pr;": "\u227A", - "prE;": "\u2AB3", - "prap;": "\u2AB7", - "prcue;": "\u227C", - "pre;": "\u2AAF", - "prec;": "\u227A", - "precapprox;": "\u2AB7", - "preccurlyeq;": "\u227C", - "preceq;": "\u2AAF", - "precnapprox;": "\u2AB9", - "precneqq;": "\u2AB5", - "precnsim;": "\u22E8", - "precsim;": "\u227E", - "prime;": "\u2032", - "primes;": "\u2119", - "prnE;": "\u2AB5", - "prnap;": "\u2AB9", - "prnsim;": "\u22E8", - "prod;": "\u220F", - "profalar;": "\u232E", - "profline;": "\u2312", - "profsurf;": "\u2313", - "prop;": "\u221D", - "propto;": "\u221D", - "prsim;": "\u227E", - "prurel;": "\u22B0", - "pscr;": "\uD835\uDCC5", - "psi;": "\u03C8", - "puncsp;": "\u2008", - "qfr;": "\uD835\uDD2E", - "qint;": "\u2A0C", - "qopf;": "\uD835\uDD62", - "qprime;": "\u2057", - "qscr;": "\uD835\uDCC6", - "quaternions;": "\u210D", - "quatint;": "\u2A16", - "quest;": "\u003F", - "questeq;": "\u225F", - "quot": "\u0022", - "quot;": "\u0022", - "rAarr;": "\u21DB", - "rArr;": "\u21D2", - "rAtail;": "\u291C", - "rBarr;": "\u290F", - "rHar;": "\u2964", - "race;": "\u223D\u0331", - "racute;": "\u0155", - "radic;": "\u221A", - "raemptyv;": "\u29B3", - "rang;": "\u27E9", - "rangd;": "\u2992", - "range;": "\u29A5", - "rangle;": "\u27E9", - "raquo": "\u00BB", - "raquo;": "\u00BB", - "rarr;": "\u2192", - "rarrap;": "\u2975", - "rarrb;": "\u21E5", - "rarrbfs;": "\u2920", - "rarrc;": "\u2933", - "rarrfs;": "\u291E", - "rarrhk;": "\u21AA", - "rarrlp;": "\u21AC", - "rarrpl;": "\u2945", - "rarrsim;": "\u2974", - "rarrtl;": "\u21A3", - "rarrw;": "\u219D", - "ratail;": "\u291A", - "ratio;": "\u2236", - "rationals;": "\u211A", - "rbarr;": "\u290D", - "rbbrk;": "\u2773", - "rbrace;": "\u007D", - "rbrack;": "\u005D", - "rbrke;": "\u298C", - "rbrksld;": "\u298E", - "rbrkslu;": "\u2990", - "rcaron;": "\u0159", - "rcedil;": "\u0157", - "rceil;": "\u2309", - "rcub;": "\u007D", - "rcy;": "\u0440", - "rdca;": "\u2937", - "rdldhar;": "\u2969", - "rdquo;": "\u201D", - "rdquor;": "\u201D", - "rdsh;": "\u21B3", - "real;": "\u211C", - "realine;": "\u211B", - "realpart;": "\u211C", - "reals;": "\u211D", - "rect;": "\u25AD", - "reg": "\u00AE", - "reg;": "\u00AE", - "rfisht;": "\u297D", - "rfloor;": "\u230B", - "rfr;": "\uD835\uDD2F", - "rhard;": "\u21C1", - "rharu;": "\u21C0", - "rharul;": "\u296C", - "rho;": "\u03C1", - "rhov;": "\u03F1", - "rightarrow;": "\u2192", - "rightarrowtail;": "\u21A3", - "rightharpoondown;": "\u21C1", - "rightharpoonup;": "\u21C0", - "rightleftarrows;": "\u21C4", - "rightleftharpoons;": "\u21CC", - "rightrightarrows;": "\u21C9", - "rightsquigarrow;": "\u219D", - "rightthreetimes;": "\u22CC", - "ring;": "\u02DA", - "risingdotseq;": "\u2253", - "rlarr;": "\u21C4", - "rlhar;": "\u21CC", - "rlm;": "\u200F", - "rmoust;": "\u23B1", - "rmoustache;": "\u23B1", - "rnmid;": "\u2AEE", - "roang;": "\u27ED", - "roarr;": "\u21FE", - "robrk;": "\u27E7", - "ropar;": "\u2986", - "ropf;": "\uD835\uDD63", - "roplus;": "\u2A2E", - "rotimes;": "\u2A35", - "rpar;": "\u0029", - "rpargt;": "\u2994", - "rppolint;": "\u2A12", - "rrarr;": "\u21C9", - "rsaquo;": "\u203A", - "rscr;": "\uD835\uDCC7", - "rsh;": "\u21B1", - "rsqb;": "\u005D", - "rsquo;": "\u2019", - "rsquor;": "\u2019", - "rthree;": "\u22CC", - "rtimes;": "\u22CA", - "rtri;": "\u25B9", - "rtrie;": "\u22B5", - "rtrif;": "\u25B8", - "rtriltri;": "\u29CE", - "ruluhar;": "\u2968", - "rx;": "\u211E", - "sacute;": "\u015B", - "sbquo;": "\u201A", - "sc;": "\u227B", - "scE;": "\u2AB4", - "scap;": "\u2AB8", - "scaron;": "\u0161", - "sccue;": "\u227D", - "sce;": "\u2AB0", - "scedil;": "\u015F", - "scirc;": "\u015D", - "scnE;": "\u2AB6", - "scnap;": "\u2ABA", - "scnsim;": "\u22E9", - "scpolint;": "\u2A13", - "scsim;": "\u227F", - "scy;": "\u0441", - "sdot;": "\u22C5", - "sdotb;": "\u22A1", - "sdote;": "\u2A66", - "seArr;": "\u21D8", - "searhk;": "\u2925", - "searr;": "\u2198", - "searrow;": "\u2198", - "sect": "\u00A7", - "sect;": "\u00A7", - "semi;": "\u003B", - "seswar;": "\u2929", - "setminus;": "\u2216", - "setmn;": "\u2216", - "sext;": "\u2736", - "sfr;": "\uD835\uDD30", - "sfrown;": "\u2322", - "sharp;": "\u266F", - "shchcy;": "\u0449", - "shcy;": "\u0448", - "shortmid;": "\u2223", - "shortparallel;": "\u2225", - "shy": "\u00AD", - "shy;": "\u00AD", - "sigma;": "\u03C3", - "sigmaf;": "\u03C2", - "sigmav;": "\u03C2", - "sim;": "\u223C", - "simdot;": "\u2A6A", - "sime;": "\u2243", - "simeq;": "\u2243", - "simg;": "\u2A9E", - "simgE;": "\u2AA0", - "siml;": "\u2A9D", - "simlE;": "\u2A9F", - "simne;": "\u2246", - "simplus;": "\u2A24", - "simrarr;": "\u2972", - "slarr;": "\u2190", - "smallsetminus;": "\u2216", - "smashp;": "\u2A33", - "smeparsl;": "\u29E4", - "smid;": "\u2223", - "smile;": "\u2323", - "smt;": "\u2AAA", - "smte;": "\u2AAC", - "smtes;": "\u2AAC\uFE00", - "softcy;": "\u044C", - "sol;": "\u002F", - "solb;": "\u29C4", - "solbar;": "\u233F", - "sopf;": "\uD835\uDD64", - "spades;": "\u2660", - "spadesuit;": "\u2660", - "spar;": "\u2225", - "sqcap;": "\u2293", - "sqcaps;": "\u2293\uFE00", - "sqcup;": "\u2294", - "sqcups;": "\u2294\uFE00", - "sqsub;": "\u228F", - "sqsube;": "\u2291", - "sqsubset;": "\u228F", - "sqsubseteq;": "\u2291", - "sqsup;": "\u2290", - "sqsupe;": "\u2292", - "sqsupset;": "\u2290", - "sqsupseteq;": "\u2292", - "squ;": "\u25A1", - "square;": "\u25A1", - "squarf;": "\u25AA", - "squf;": "\u25AA", - "srarr;": "\u2192", - "sscr;": "\uD835\uDCC8", - "ssetmn;": "\u2216", - "ssmile;": "\u2323", - "sstarf;": "\u22C6", - "star;": "\u2606", - "starf;": "\u2605", - "straightepsilon;": "\u03F5", - "straightphi;": "\u03D5", - "strns;": "\u00AF", - "sub;": "\u2282", - "subE;": "\u2AC5", - "subdot;": "\u2ABD", - "sube;": "\u2286", - "subedot;": "\u2AC3", - "submult;": "\u2AC1", - "subnE;": "\u2ACB", - "subne;": "\u228A", - "subplus;": "\u2ABF", - "subrarr;": "\u2979", - "subset;": "\u2282", - "subseteq;": "\u2286", - "subseteqq;": "\u2AC5", - "subsetneq;": "\u228A", - "subsetneqq;": "\u2ACB", - "subsim;": "\u2AC7", - "subsub;": "\u2AD5", - "subsup;": "\u2AD3", - "succ;": "\u227B", - "succapprox;": "\u2AB8", - "succcurlyeq;": "\u227D", - "succeq;": "\u2AB0", - "succnapprox;": "\u2ABA", - "succneqq;": "\u2AB6", - "succnsim;": "\u22E9", - "succsim;": "\u227F", - "sum;": "\u2211", - "sung;": "\u266A", - "sup1": "\u00B9", - "sup1;": "\u00B9", - "sup2": "\u00B2", - "sup2;": "\u00B2", - "sup3": "\u00B3", - "sup3;": "\u00B3", - "sup;": "\u2283", - "supE;": "\u2AC6", - "supdot;": "\u2ABE", - "supdsub;": "\u2AD8", - "supe;": "\u2287", - "supedot;": "\u2AC4", - "suphsol;": "\u27C9", - "suphsub;": "\u2AD7", - "suplarr;": "\u297B", - "supmult;": "\u2AC2", - "supnE;": "\u2ACC", - "supne;": "\u228B", - "supplus;": "\u2AC0", - "supset;": "\u2283", - "supseteq;": "\u2287", - "supseteqq;": "\u2AC6", - "supsetneq;": "\u228B", - "supsetneqq;": "\u2ACC", - "supsim;": "\u2AC8", - "supsub;": "\u2AD4", - "supsup;": "\u2AD6", - "swArr;": "\u21D9", - "swarhk;": "\u2926", - "swarr;": "\u2199", - "swarrow;": "\u2199", - "swnwar;": "\u292A", - "szlig": "\u00DF", - "szlig;": "\u00DF", - "target;": "\u2316", - "tau;": "\u03C4", - "tbrk;": "\u23B4", - "tcaron;": "\u0165", - "tcedil;": "\u0163", - "tcy;": "\u0442", - "tdot;": "\u20DB", - "telrec;": "\u2315", - "tfr;": "\uD835\uDD31", - "there4;": "\u2234", - "therefore;": "\u2234", - "theta;": "\u03B8", - "thetasym;": "\u03D1", - "thetav;": "\u03D1", - "thickapprox;": "\u2248", - "thicksim;": "\u223C", - "thinsp;": "\u2009", - "thkap;": "\u2248", - "thksim;": "\u223C", - "thorn": "\u00FE", - "thorn;": "\u00FE", - "tilde;": "\u02DC", - "times": "\u00D7", - "times;": "\u00D7", - "timesb;": "\u22A0", - "timesbar;": "\u2A31", - "timesd;": "\u2A30", - "tint;": "\u222D", - "toea;": "\u2928", - "top;": "\u22A4", - "topbot;": "\u2336", - "topcir;": "\u2AF1", - "topf;": "\uD835\uDD65", - "topfork;": "\u2ADA", - "tosa;": "\u2929", - "tprime;": "\u2034", - "trade;": "\u2122", - "triangle;": "\u25B5", - "triangledown;": "\u25BF", - "triangleleft;": "\u25C3", - "trianglelefteq;": "\u22B4", - "triangleq;": "\u225C", - "triangleright;": "\u25B9", - "trianglerighteq;": "\u22B5", - "tridot;": "\u25EC", - "trie;": "\u225C", - "triminus;": "\u2A3A", - "triplus;": "\u2A39", - "trisb;": "\u29CD", - "tritime;": "\u2A3B", - "trpezium;": "\u23E2", - "tscr;": "\uD835\uDCC9", - "tscy;": "\u0446", - "tshcy;": "\u045B", - "tstrok;": "\u0167", - "twixt;": "\u226C", - "twoheadleftarrow;": "\u219E", - "twoheadrightarrow;": "\u21A0", - "uArr;": "\u21D1", - "uHar;": "\u2963", - "uacute": "\u00FA", - "uacute;": "\u00FA", - "uarr;": "\u2191", - "ubrcy;": "\u045E", - "ubreve;": "\u016D", - "ucirc": "\u00FB", - "ucirc;": "\u00FB", - "ucy;": "\u0443", - "udarr;": "\u21C5", - "udblac;": "\u0171", - "udhar;": "\u296E", - "ufisht;": "\u297E", - "ufr;": "\uD835\uDD32", - "ugrave": "\u00F9", - "ugrave;": "\u00F9", - "uharl;": "\u21BF", - "uharr;": "\u21BE", - "uhblk;": "\u2580", - "ulcorn;": "\u231C", - "ulcorner;": "\u231C", - "ulcrop;": "\u230F", - "ultri;": "\u25F8", - "umacr;": "\u016B", - "uml": "\u00A8", - "uml;": "\u00A8", - "uogon;": "\u0173", - "uopf;": "\uD835\uDD66", - "uparrow;": "\u2191", - "updownarrow;": "\u2195", - "upharpoonleft;": "\u21BF", - "upharpoonright;": "\u21BE", - "uplus;": "\u228E", - "upsi;": "\u03C5", - "upsih;": "\u03D2", - "upsilon;": "\u03C5", - "upuparrows;": "\u21C8", - "urcorn;": "\u231D", - "urcorner;": "\u231D", - "urcrop;": "\u230E", - "uring;": "\u016F", - "urtri;": "\u25F9", - "uscr;": "\uD835\uDCCA", - "utdot;": "\u22F0", - "utilde;": "\u0169", - "utri;": "\u25B5", - "utrif;": "\u25B4", - "uuarr;": "\u21C8", - "uuml": "\u00FC", - "uuml;": "\u00FC", - "uwangle;": "\u29A7", - "vArr;": "\u21D5", - "vBar;": "\u2AE8", - "vBarv;": "\u2AE9", - "vDash;": "\u22A8", - "vangrt;": "\u299C", - "varepsilon;": "\u03F5", - "varkappa;": "\u03F0", - "varnothing;": "\u2205", - "varphi;": "\u03D5", - "varpi;": "\u03D6", - "varpropto;": "\u221D", - "varr;": "\u2195", - "varrho;": "\u03F1", - "varsigma;": "\u03C2", - "varsubsetneq;": "\u228A\uFE00", - "varsubsetneqq;": "\u2ACB\uFE00", - "varsupsetneq;": "\u228B\uFE00", - "varsupsetneqq;": "\u2ACC\uFE00", - "vartheta;": "\u03D1", - "vartriangleleft;": "\u22B2", - "vartriangleright;": "\u22B3", - "vcy;": "\u0432", - "vdash;": "\u22A2", - "vee;": "\u2228", - "veebar;": "\u22BB", - "veeeq;": "\u225A", - "vellip;": "\u22EE", - "verbar;": "\u007C", - "vert;": "\u007C", - "vfr;": "\uD835\uDD33", - "vltri;": "\u22B2", - "vnsub;": "\u2282\u20D2", - "vnsup;": "\u2283\u20D2", - "vopf;": "\uD835\uDD67", - "vprop;": "\u221D", - "vrtri;": "\u22B3", - "vscr;": "\uD835\uDCCB", - "vsubnE;": "\u2ACB\uFE00", - "vsubne;": "\u228A\uFE00", - "vsupnE;": "\u2ACC\uFE00", - "vsupne;": "\u228B\uFE00", - "vzigzag;": "\u299A", - "wcirc;": "\u0175", - "wedbar;": "\u2A5F", - "wedge;": "\u2227", - "wedgeq;": "\u2259", - "weierp;": "\u2118", - "wfr;": "\uD835\uDD34", - "wopf;": "\uD835\uDD68", - "wp;": "\u2118", - "wr;": "\u2240", - "wreath;": "\u2240", - "wscr;": "\uD835\uDCCC", - "xcap;": "\u22C2", - "xcirc;": "\u25EF", - "xcup;": "\u22C3", - "xdtri;": "\u25BD", - "xfr;": "\uD835\uDD35", - "xhArr;": "\u27FA", - "xharr;": "\u27F7", - "xi;": "\u03BE", - "xlArr;": "\u27F8", - "xlarr;": "\u27F5", - "xmap;": "\u27FC", - "xnis;": "\u22FB", - "xodot;": "\u2A00", - "xopf;": "\uD835\uDD69", - "xoplus;": "\u2A01", - "xotime;": "\u2A02", - "xrArr;": "\u27F9", - "xrarr;": "\u27F6", - "xscr;": "\uD835\uDCCD", - "xsqcup;": "\u2A06", - "xuplus;": "\u2A04", - "xutri;": "\u25B3", - "xvee;": "\u22C1", - "xwedge;": "\u22C0", - "yacute": "\u00FD", - "yacute;": "\u00FD", - "yacy;": "\u044F", - "ycirc;": "\u0177", - "ycy;": "\u044B", - "yen": "\u00A5", - "yen;": "\u00A5", - "yfr;": "\uD835\uDD36", - "yicy;": "\u0457", - "yopf;": "\uD835\uDD6A", - "yscr;": "\uD835\uDCCE", - "yucy;": "\u044E", - "yuml": "\u00FF", - "yuml;": "\u00FF", - "zacute;": "\u017A", - "zcaron;": "\u017E", - "zcy;": "\u0437", - "zdot;": "\u017C", - "zeetrf;": "\u2128", - "zeta;": "\u03B6", - "zfr;": "\uD835\uDD37", - "zhcy;": "\u0436", - "zigrarr;": "\u21DD", - "zopf;": "\uD835\uDD6B", - "zscr;": "\uD835\uDCCF", - "zwj;": "\u200D", - "zwnj;": "\u200C" + "AElig": { "p": [198], "c": "\u00C6" }, + "AElig;": { "p": [198], "c": "\u00C6" }, + "AMP": { "p": [38], "c": "\u0026" }, + "AMP;": { "p": [38], "c": "\u0026" }, + "Aacute": { "p": [193], "c": "\u00C1" }, + "Aacute;": { "p": [193], "c": "\u00C1" }, + "Abreve;": { "p": [258], "c": "\u0102" }, + "Acirc": { "p": [194], "c": "\u00C2" }, + "Acirc;": { "p": [194], "c": "\u00C2" }, + "Acy;": { "p": [1040], "c": "\u0410" }, + "Afr;": { "p": [120068], "c": "\uD835\uDD04" }, + "Agrave": { "p": [192], "c": "\u00C0" }, + "Agrave;": { "p": [192], "c": "\u00C0" }, + "Alpha;": { "p": [913], "c": "\u0391" }, + "Amacr;": { "p": [256], "c": "\u0100" }, + "And;": { "p": [10835], "c": "\u2A53" }, + "Aogon;": { "p": [260], "c": "\u0104" }, + "Aopf;": { "p": [120120], "c": "\uD835\uDD38" }, + "ApplyFunction;": { "p": [8289], "c": "\u2061" }, + "Aring": { "p": [197], "c": "\u00C5" }, + "Aring;": { "p": [197], "c": "\u00C5" }, + "Ascr;": { "p": [119964], "c": "\uD835\uDC9C" }, + "Assign;": { "p": [8788], "c": "\u2254" }, + "Atilde": { "p": [195], "c": "\u00C3" }, + "Atilde;": { "p": [195], "c": "\u00C3" }, + "Auml": { "p": [196], "c": "\u00C4" }, + "Auml;": { "p": [196], "c": "\u00C4" }, + "Backslash;": { "p": [8726], "c": "\u2216" }, + "Barv;": { "p": [10983], "c": "\u2AE7" }, + "Barwed;": { "p": [8966], "c": "\u2306" }, + "Bcy;": { "p": [1041], "c": "\u0411" }, + "Because;": { "p": [8757], "c": "\u2235" }, + "Bernoullis;": { "p": [8492], "c": "\u212C" }, + "Beta;": { "p": [914], "c": "\u0392" }, + "Bfr;": { "p": [120069], "c": "\uD835\uDD05" }, + "Bopf;": { "p": [120121], "c": "\uD835\uDD39" }, + "Breve;": { "p": [728], "c": "\u02D8" }, + "Bscr;": { "p": [8492], "c": "\u212C" }, + "Bumpeq;": { "p": [8782], "c": "\u224E" }, + "CHcy;": { "p": [1063], "c": "\u0427" }, + "COPY": { "p": [169], "c": "\u00A9" }, + "COPY;": { "p": [169], "c": "\u00A9" }, + "Cacute;": { "p": [262], "c": "\u0106" }, + "Cap;": { "p": [8914], "c": "\u22D2" }, + "CapitalDifferentialD;": { "p": [8517], "c": "\u2145" }, + "Cayleys;": { "p": [8493], "c": "\u212D" }, + "Ccaron;": { "p": [268], "c": "\u010C" }, + "Ccedil": { "p": [199], "c": "\u00C7" }, + "Ccedil;": { "p": [199], "c": "\u00C7" }, + "Ccirc;": { "p": [264], "c": "\u0108" }, + "Cconint;": { "p": [8752], "c": "\u2230" }, + "Cdot;": { "p": [266], "c": "\u010A" }, + "Cedilla;": { "p": [184], "c": "\u00B8" }, + "CenterDot;": { "p": [183], "c": "\u00B7" }, + "Cfr;": { "p": [8493], "c": "\u212D" }, + "Chi;": { "p": [935], "c": "\u03A7" }, + "CircleDot;": { "p": [8857], "c": "\u2299" }, + "CircleMinus;": { "p": [8854], "c": "\u2296" }, + "CirclePlus;": { "p": [8853], "c": "\u2295" }, + "CircleTimes;": { "p": [8855], "c": "\u2297" }, + "ClockwiseContourIntegral;": { + "p": [8754], + "c": "\u2232" + }, + "CloseCurlyDoubleQuote;": { "p": [8221], "c": "\u201D" }, + "CloseCurlyQuote;": { "p": [8217], "c": "\u2019" }, + "Colon;": { "p": [8759], "c": "\u2237" }, + "Colone;": { "p": [10868], "c": "\u2A74" }, + "Congruent;": { "p": [8801], "c": "\u2261" }, + "Conint;": { "p": [8751], "c": "\u222F" }, + "ContourIntegral;": { "p": [8750], "c": "\u222E" }, + "Copf;": { "p": [8450], "c": "\u2102" }, + "Coproduct;": { "p": [8720], "c": "\u2210" }, + "CounterClockwiseContourIntegral;": { + "p": [8755], + "c": "\u2233" + }, + "Cross;": { "p": [10799], "c": "\u2A2F" }, + "Cscr;": { "p": [119966], "c": "\uD835\uDC9E" }, + "Cup;": { "p": [8915], "c": "\u22D3" }, + "CupCap;": { "p": [8781], "c": "\u224D" }, + "DD;": { "p": [8517], "c": "\u2145" }, + "DDotrahd;": { "p": [10513], "c": "\u2911" }, + "DJcy;": { "p": [1026], "c": "\u0402" }, + "DScy;": { "p": [1029], "c": "\u0405" }, + "DZcy;": { "p": [1039], "c": "\u040F" }, + "Dagger;": { "p": [8225], "c": "\u2021" }, + "Darr;": { "p": [8609], "c": "\u21A1" }, + "Dashv;": { "p": [10980], "c": "\u2AE4" }, + "Dcaron;": { "p": [270], "c": "\u010E" }, + "Dcy;": { "p": [1044], "c": "\u0414" }, + "Del;": { "p": [8711], "c": "\u2207" }, + "Delta;": { "p": [916], "c": "\u0394" }, + "Dfr;": { "p": [120071], "c": "\uD835\uDD07" }, + "DiacriticalAcute;": { "p": [180], "c": "\u00B4" }, + "DiacriticalDot;": { "p": [729], "c": "\u02D9" }, + "DiacriticalDoubleAcute;": { "p": [733], "c": "\u02DD" }, + "DiacriticalGrave;": { "p": [96], "c": "\u0060" }, + "DiacriticalTilde;": { "p": [732], "c": "\u02DC" }, + "Diamond;": { "p": [8900], "c": "\u22C4" }, + "DifferentialD;": { "p": [8518], "c": "\u2146" }, + "Dopf;": { "p": [120123], "c": "\uD835\uDD3B" }, + "Dot;": { "p": [168], "c": "\u00A8" }, + "DotDot;": { "p": [8412], "c": "\u20DC" }, + "DotEqual;": { "p": [8784], "c": "\u2250" }, + "DoubleContourIntegral;": { "p": [8751], "c": "\u222F" }, + "DoubleDot;": { "p": [168], "c": "\u00A8" }, + "DoubleDownArrow;": { "p": [8659], "c": "\u21D3" }, + "DoubleLeftArrow;": { "p": [8656], "c": "\u21D0" }, + "DoubleLeftRightArrow;": { "p": [8660], "c": "\u21D4" }, + "DoubleLeftTee;": { "p": [10980], "c": "\u2AE4" }, + "DoubleLongLeftArrow;": { "p": [10232], "c": "\u27F8" }, + "DoubleLongLeftRightArrow;": { + "p": [10234], + "c": "\u27FA" + }, + "DoubleLongRightArrow;": { "p": [10233], "c": "\u27F9" }, + "DoubleRightArrow;": { "p": [8658], "c": "\u21D2" }, + "DoubleRightTee;": { "p": [8872], "c": "\u22A8" }, + "DoubleUpArrow;": { "p": [8657], "c": "\u21D1" }, + "DoubleUpDownArrow;": { "p": [8661], "c": "\u21D5" }, + "DoubleVerticalBar;": { "p": [8741], "c": "\u2225" }, + "DownArrow;": { "p": [8595], "c": "\u2193" }, + "DownArrowBar;": { "p": [10515], "c": "\u2913" }, + "DownArrowUpArrow;": { "p": [8693], "c": "\u21F5" }, + "DownBreve;": { "p": [785], "c": "\u0311" }, + "DownLeftRightVector;": { "p": [10576], "c": "\u2950" }, + "DownLeftTeeVector;": { "p": [10590], "c": "\u295E" }, + "DownLeftVector;": { "p": [8637], "c": "\u21BD" }, + "DownLeftVectorBar;": { "p": [10582], "c": "\u2956" }, + "DownRightTeeVector;": { "p": [10591], "c": "\u295F" }, + "DownRightVector;": { "p": [8641], "c": "\u21C1" }, + "DownRightVectorBar;": { "p": [10583], "c": "\u2957" }, + "DownTee;": { "p": [8868], "c": "\u22A4" }, + "DownTeeArrow;": { "p": [8615], "c": "\u21A7" }, + "Downarrow;": { "p": [8659], "c": "\u21D3" }, + "Dscr;": { "p": [119967], "c": "\uD835\uDC9F" }, + "Dstrok;": { "p": [272], "c": "\u0110" }, + "ENG;": { "p": [330], "c": "\u014A" }, + "ETH": { "p": [208], "c": "\u00D0" }, + "ETH;": { "p": [208], "c": "\u00D0" }, + "Eacute": { "p": [201], "c": "\u00C9" }, + "Eacute;": { "p": [201], "c": "\u00C9" }, + "Ecaron;": { "p": [282], "c": "\u011A" }, + "Ecirc": { "p": [202], "c": "\u00CA" }, + "Ecirc;": { "p": [202], "c": "\u00CA" }, + "Ecy;": { "p": [1069], "c": "\u042D" }, + "Edot;": { "p": [278], "c": "\u0116" }, + "Efr;": { "p": [120072], "c": "\uD835\uDD08" }, + "Egrave": { "p": [200], "c": "\u00C8" }, + "Egrave;": { "p": [200], "c": "\u00C8" }, + "Element;": { "p": [8712], "c": "\u2208" }, + "Emacr;": { "p": [274], "c": "\u0112" }, + "EmptySmallSquare;": { "p": [9723], "c": "\u25FB" }, + "EmptyVerySmallSquare;": { "p": [9643], "c": "\u25AB" }, + "Eogon;": { "p": [280], "c": "\u0118" }, + "Eopf;": { "p": [120124], "c": "\uD835\uDD3C" }, + "Epsilon;": { "p": [917], "c": "\u0395" }, + "Equal;": { "p": [10869], "c": "\u2A75" }, + "EqualTilde;": { "p": [8770], "c": "\u2242" }, + "Equilibrium;": { "p": [8652], "c": "\u21CC" }, + "Escr;": { "p": [8496], "c": "\u2130" }, + "Esim;": { "p": [10867], "c": "\u2A73" }, + "Eta;": { "p": [919], "c": "\u0397" }, + "Euml": { "p": [203], "c": "\u00CB" }, + "Euml;": { "p": [203], "c": "\u00CB" }, + "Exists;": { "p": [8707], "c": "\u2203" }, + "ExponentialE;": { "p": [8519], "c": "\u2147" }, + "Fcy;": { "p": [1060], "c": "\u0424" }, + "Ffr;": { "p": [120073], "c": "\uD835\uDD09" }, + "FilledSmallSquare;": { "p": [9724], "c": "\u25FC" }, + "FilledVerySmallSquare;": { "p": [9642], "c": "\u25AA" }, + "Fopf;": { "p": [120125], "c": "\uD835\uDD3D" }, + "ForAll;": { "p": [8704], "c": "\u2200" }, + "Fouriertrf;": { "p": [8497], "c": "\u2131" }, + "Fscr;": { "p": [8497], "c": "\u2131" }, + "GJcy;": { "p": [1027], "c": "\u0403" }, + "GT": { "p": [62], "c": "\u003E" }, + "GT;": { "p": [62], "c": "\u003E" }, + "Gamma;": { "p": [915], "c": "\u0393" }, + "Gammad;": { "p": [988], "c": "\u03DC" }, + "Gbreve;": { "p": [286], "c": "\u011E" }, + "Gcedil;": { "p": [290], "c": "\u0122" }, + "Gcirc;": { "p": [284], "c": "\u011C" }, + "Gcy;": { "p": [1043], "c": "\u0413" }, + "Gdot;": { "p": [288], "c": "\u0120" }, + "Gfr;": { "p": [120074], "c": "\uD835\uDD0A" }, + "Gg;": { "p": [8921], "c": "\u22D9" }, + "Gopf;": { "p": [120126], "c": "\uD835\uDD3E" }, + "GreaterEqual;": { "p": [8805], "c": "\u2265" }, + "GreaterEqualLess;": { "p": [8923], "c": "\u22DB" }, + "GreaterFullEqual;": { "p": [8807], "c": "\u2267" }, + "GreaterGreater;": { "p": [10914], "c": "\u2AA2" }, + "GreaterLess;": { "p": [8823], "c": "\u2277" }, + "GreaterSlantEqual;": { "p": [10878], "c": "\u2A7E" }, + "GreaterTilde;": { "p": [8819], "c": "\u2273" }, + "Gscr;": { "p": [119970], "c": "\uD835\uDCA2" }, + "Gt;": { "p": [8811], "c": "\u226B" }, + "HARDcy;": { "p": [1066], "c": "\u042A" }, + "Hacek;": { "p": [711], "c": "\u02C7" }, + "Hat;": { "p": [94], "c": "\u005E" }, + "Hcirc;": { "p": [292], "c": "\u0124" }, + "Hfr;": { "p": [8460], "c": "\u210C" }, + "HilbertSpace;": { "p": [8459], "c": "\u210B" }, + "Hopf;": { "p": [8461], "c": "\u210D" }, + "HorizontalLine;": { "p": [9472], "c": "\u2500" }, + "Hscr;": { "p": [8459], "c": "\u210B" }, + "Hstrok;": { "p": [294], "c": "\u0126" }, + "HumpDownHump;": { "p": [8782], "c": "\u224E" }, + "HumpEqual;": { "p": [8783], "c": "\u224F" }, + "IEcy;": { "p": [1045], "c": "\u0415" }, + "IJlig;": { "p": [306], "c": "\u0132" }, + "IOcy;": { "p": [1025], "c": "\u0401" }, + "Iacute": { "p": [205], "c": "\u00CD" }, + "Iacute;": { "p": [205], "c": "\u00CD" }, + "Icirc": { "p": [206], "c": "\u00CE" }, + "Icirc;": { "p": [206], "c": "\u00CE" }, + "Icy;": { "p": [1048], "c": "\u0418" }, + "Idot;": { "p": [304], "c": "\u0130" }, + "Ifr;": { "p": [8465], "c": "\u2111" }, + "Igrave": { "p": [204], "c": "\u00CC" }, + "Igrave;": { "p": [204], "c": "\u00CC" }, + "Im;": { "p": [8465], "c": "\u2111" }, + "Imacr;": { "p": [298], "c": "\u012A" }, + "ImaginaryI;": { "p": [8520], "c": "\u2148" }, + "Implies;": { "p": [8658], "c": "\u21D2" }, + "Int;": { "p": [8748], "c": "\u222C" }, + "Integral;": { "p": [8747], "c": "\u222B" }, + "Intersection;": { "p": [8898], "c": "\u22C2" }, + "InvisibleComma;": { "p": [8291], "c": "\u2063" }, + "InvisibleTimes;": { "p": [8290], "c": "\u2062" }, + "Iogon;": { "p": [302], "c": "\u012E" }, + "Iopf;": { "p": [120128], "c": "\uD835\uDD40" }, + "Iota;": { "p": [921], "c": "\u0399" }, + "Iscr;": { "p": [8464], "c": "\u2110" }, + "Itilde;": { "p": [296], "c": "\u0128" }, + "Iukcy;": { "p": [1030], "c": "\u0406" }, + "Iuml": { "p": [207], "c": "\u00CF" }, + "Iuml;": { "p": [207], "c": "\u00CF" }, + "Jcirc;": { "p": [308], "c": "\u0134" }, + "Jcy;": { "p": [1049], "c": "\u0419" }, + "Jfr;": { "p": [120077], "c": "\uD835\uDD0D" }, + "Jopf;": { "p": [120129], "c": "\uD835\uDD41" }, + "Jscr;": { "p": [119973], "c": "\uD835\uDCA5" }, + "Jsercy;": { "p": [1032], "c": "\u0408" }, + "Jukcy;": { "p": [1028], "c": "\u0404" }, + "KHcy;": { "p": [1061], "c": "\u0425" }, + "KJcy;": { "p": [1036], "c": "\u040C" }, + "Kappa;": { "p": [922], "c": "\u039A" }, + "Kcedil;": { "p": [310], "c": "\u0136" }, + "Kcy;": { "p": [1050], "c": "\u041A" }, + "Kfr;": { "p": [120078], "c": "\uD835\uDD0E" }, + "Kopf;": { "p": [120130], "c": "\uD835\uDD42" }, + "Kscr;": { "p": [119974], "c": "\uD835\uDCA6" }, + "LJcy;": { "p": [1033], "c": "\u0409" }, + "LT": { "p": [60], "c": "\u003C" }, + "LT;": { "p": [60], "c": "\u003C" }, + "Lacute;": { "p": [313], "c": "\u0139" }, + "Lambda;": { "p": [923], "c": "\u039B" }, + "Lang;": { "p": [10218], "c": "\u27EA" }, + "Laplacetrf;": { "p": [8466], "c": "\u2112" }, + "Larr;": { "p": [8606], "c": "\u219E" }, + "Lcaron;": { "p": [317], "c": "\u013D" }, + "Lcedil;": { "p": [315], "c": "\u013B" }, + "Lcy;": { "p": [1051], "c": "\u041B" }, + "LeftAngleBracket;": { "p": [10216], "c": "\u27E8" }, + "LeftArrow;": { "p": [8592], "c": "\u2190" }, + "LeftArrowBar;": { "p": [8676], "c": "\u21E4" }, + "LeftArrowRightArrow;": { "p": [8646], "c": "\u21C6" }, + "LeftCeiling;": { "p": [8968], "c": "\u2308" }, + "LeftDoubleBracket;": { "p": [10214], "c": "\u27E6" }, + "LeftDownTeeVector;": { "p": [10593], "c": "\u2961" }, + "LeftDownVector;": { "p": [8643], "c": "\u21C3" }, + "LeftDownVectorBar;": { "p": [10585], "c": "\u2959" }, + "LeftFloor;": { "p": [8970], "c": "\u230A" }, + "LeftRightArrow;": { "p": [8596], "c": "\u2194" }, + "LeftRightVector;": { "p": [10574], "c": "\u294E" }, + "LeftTee;": { "p": [8867], "c": "\u22A3" }, + "LeftTeeArrow;": { "p": [8612], "c": "\u21A4" }, + "LeftTeeVector;": { "p": [10586], "c": "\u295A" }, + "LeftTriangle;": { "p": [8882], "c": "\u22B2" }, + "LeftTriangleBar;": { "p": [10703], "c": "\u29CF" }, + "LeftTriangleEqual;": { "p": [8884], "c": "\u22B4" }, + "LeftUpDownVector;": { "p": [10577], "c": "\u2951" }, + "LeftUpTeeVector;": { "p": [10592], "c": "\u2960" }, + "LeftUpVector;": { "p": [8639], "c": "\u21BF" }, + "LeftUpVectorBar;": { "p": [10584], "c": "\u2958" }, + "LeftVector;": { "p": [8636], "c": "\u21BC" }, + "LeftVectorBar;": { "p": [10578], "c": "\u2952" }, + "Leftarrow;": { "p": [8656], "c": "\u21D0" }, + "Leftrightarrow;": { "p": [8660], "c": "\u21D4" }, + "LessEqualGreater;": { "p": [8922], "c": "\u22DA" }, + "LessFullEqual;": { "p": [8806], "c": "\u2266" }, + "LessGreater;": { "p": [8822], "c": "\u2276" }, + "LessLess;": { "p": [10913], "c": "\u2AA1" }, + "LessSlantEqual;": { "p": [10877], "c": "\u2A7D" }, + "LessTilde;": { "p": [8818], "c": "\u2272" }, + "Lfr;": { "p": [120079], "c": "\uD835\uDD0F" }, + "Ll;": { "p": [8920], "c": "\u22D8" }, + "Lleftarrow;": { "p": [8666], "c": "\u21DA" }, + "Lmidot;": { "p": [319], "c": "\u013F" }, + "LongLeftArrow;": { "p": [10229], "c": "\u27F5" }, + "LongLeftRightArrow;": { "p": [10231], "c": "\u27F7" }, + "LongRightArrow;": { "p": [10230], "c": "\u27F6" }, + "Longleftarrow;": { "p": [10232], "c": "\u27F8" }, + "Longleftrightarrow;": { "p": [10234], "c": "\u27FA" }, + "Longrightarrow;": { "p": [10233], "c": "\u27F9" }, + "Lopf;": { "p": [120131], "c": "\uD835\uDD43" }, + "LowerLeftArrow;": { "p": [8601], "c": "\u2199" }, + "LowerRightArrow;": { "p": [8600], "c": "\u2198" }, + "Lscr;": { "p": [8466], "c": "\u2112" }, + "Lsh;": { "p": [8624], "c": "\u21B0" }, + "Lstrok;": { "p": [321], "c": "\u0141" }, + "Lt;": { "p": [8810], "c": "\u226A" }, + "Map;": { "p": [10501], "c": "\u2905" }, + "Mcy;": { "p": [1052], "c": "\u041C" }, + "MediumSpace;": { "p": [8287], "c": "\u205F" }, + "Mellintrf;": { "p": [8499], "c": "\u2133" }, + "Mfr;": { "p": [120080], "c": "\uD835\uDD10" }, + "MinusPlus;": { "p": [8723], "c": "\u2213" }, + "Mopf;": { "p": [120132], "c": "\uD835\uDD44" }, + "Mscr;": { "p": [8499], "c": "\u2133" }, + "Mu;": { "p": [924], "c": "\u039C" }, + "NJcy;": { "p": [1034], "c": "\u040A" }, + "Nacute;": { "p": [323], "c": "\u0143" }, + "Ncaron;": { "p": [327], "c": "\u0147" }, + "Ncedil;": { "p": [325], "c": "\u0145" }, + "Ncy;": { "p": [1053], "c": "\u041D" }, + "NegativeMediumSpace;": { "p": [8203], "c": "\u200B" }, + "NegativeThickSpace;": { "p": [8203], "c": "\u200B" }, + "NegativeThinSpace;": { "p": [8203], "c": "\u200B" }, + "NegativeVeryThinSpace;": { "p": [8203], "c": "\u200B" }, + "NestedGreaterGreater;": { "p": [8811], "c": "\u226B" }, + "NestedLessLess;": { "p": [8810], "c": "\u226A" }, + "NewLine;": { "p": [10], "c": "\u000A" }, + "Nfr;": { "p": [120081], "c": "\uD835\uDD11" }, + "NoBreak;": { "p": [8288], "c": "\u2060" }, + "NonBreakingSpace;": { "p": [160], "c": "\u00A0" }, + "Nopf;": { "p": [8469], "c": "\u2115" }, + "Not;": { "p": [10988], "c": "\u2AEC" }, + "NotCongruent;": { "p": [8802], "c": "\u2262" }, + "NotCupCap;": { "p": [8813], "c": "\u226D" }, + "NotDoubleVerticalBar;": { "p": [8742], "c": "\u2226" }, + "NotElement;": { "p": [8713], "c": "\u2209" }, + "NotEqual;": { "p": [8800], "c": "\u2260" }, + "NotEqualTilde;": { + "p": [8770, 824], + "c": "\u2242\u0338" + }, + "NotExists;": { "p": [8708], "c": "\u2204" }, + "NotGreater;": { "p": [8815], "c": "\u226F" }, + "NotGreaterEqual;": { "p": [8817], "c": "\u2271" }, + "NotGreaterFullEqual;": { + "p": [8807, 824], + "c": "\u2267\u0338" + }, + "NotGreaterGreater;": { + "p": [8811, 824], + "c": "\u226B\u0338" + }, + "NotGreaterLess;": { "p": [8825], "c": "\u2279" }, + "NotGreaterSlantEqual;": { + "p": [10878, 824], + "c": "\u2A7E\u0338" + }, + "NotGreaterTilde;": { "p": [8821], "c": "\u2275" }, + "NotHumpDownHump;": { + "p": [8782, 824], + "c": "\u224E\u0338" + }, + "NotHumpEqual;": { + "p": [8783, 824], + "c": "\u224F\u0338" + }, + "NotLeftTriangle;": { "p": [8938], "c": "\u22EA" }, + "NotLeftTriangleBar;": { + "p": [10703, 824], + "c": "\u29CF\u0338" + }, + "NotLeftTriangleEqual;": { "p": [8940], "c": "\u22EC" }, + "NotLess;": { "p": [8814], "c": "\u226E" }, + "NotLessEqual;": { "p": [8816], "c": "\u2270" }, + "NotLessGreater;": { "p": [8824], "c": "\u2278" }, + "NotLessLess;": { + "p": [8810, 824], + "c": "\u226A\u0338" + }, + "NotLessSlantEqual;": { + "p": [10877, 824], + "c": "\u2A7D\u0338" + }, + "NotLessTilde;": { "p": [8820], "c": "\u2274" }, + "NotNestedGreaterGreater;": { + "p": [10914, 824], + "c": "\u2AA2\u0338" + }, + "NotNestedLessLess;": { + "p": [10913, 824], + "c": "\u2AA1\u0338" + }, + "NotPrecedes;": { "p": [8832], "c": "\u2280" }, + "NotPrecedesEqual;": { + "p": [10927, 824], + "c": "\u2AAF\u0338" + }, + "NotPrecedesSlantEqual;": { "p": [8928], "c": "\u22E0" }, + "NotReverseElement;": { "p": [8716], "c": "\u220C" }, + "NotRightTriangle;": { "p": [8939], "c": "\u22EB" }, + "NotRightTriangleBar;": { + "p": [10704, 824], + "c": "\u29D0\u0338" + }, + "NotRightTriangleEqual;": { "p": [8941], "c": "\u22ED" }, + "NotSquareSubset;": { + "p": [8847, 824], + "c": "\u228F\u0338" + }, + "NotSquareSubsetEqual;": { "p": [8930], "c": "\u22E2" }, + "NotSquareSuperset;": { + "p": [8848, 824], + "c": "\u2290\u0338" + }, + "NotSquareSupersetEqual;": { + "p": [8931], + "c": "\u22E3" + }, + "NotSubset;": { "p": [8834, 8402], "c": "\u2282\u20D2" }, + "NotSubsetEqual;": { "p": [8840], "c": "\u2288" }, + "NotSucceeds;": { "p": [8833], "c": "\u2281" }, + "NotSucceedsEqual;": { + "p": [10928, 824], + "c": "\u2AB0\u0338" + }, + "NotSucceedsSlantEqual;": { "p": [8929], "c": "\u22E1" }, + "NotSucceedsTilde;": { + "p": [8831, 824], + "c": "\u227F\u0338" + }, + "NotSuperset;": { + "p": [8835, 8402], + "c": "\u2283\u20D2" + }, + "NotSupersetEqual;": { "p": [8841], "c": "\u2289" }, + "NotTilde;": { "p": [8769], "c": "\u2241" }, + "NotTildeEqual;": { "p": [8772], "c": "\u2244" }, + "NotTildeFullEqual;": { "p": [8775], "c": "\u2247" }, + "NotTildeTilde;": { "p": [8777], "c": "\u2249" }, + "NotVerticalBar;": { "p": [8740], "c": "\u2224" }, + "Nscr;": { "p": [119977], "c": "\uD835\uDCA9" }, + "Ntilde": { "p": [209], "c": "\u00D1" }, + "Ntilde;": { "p": [209], "c": "\u00D1" }, + "Nu;": { "p": [925], "c": "\u039D" }, + "OElig;": { "p": [338], "c": "\u0152" }, + "Oacute": { "p": [211], "c": "\u00D3" }, + "Oacute;": { "p": [211], "c": "\u00D3" }, + "Ocirc": { "p": [212], "c": "\u00D4" }, + "Ocirc;": { "p": [212], "c": "\u00D4" }, + "Ocy;": { "p": [1054], "c": "\u041E" }, + "Odblac;": { "p": [336], "c": "\u0150" }, + "Ofr;": { "p": [120082], "c": "\uD835\uDD12" }, + "Ograve": { "p": [210], "c": "\u00D2" }, + "Ograve;": { "p": [210], "c": "\u00D2" }, + "Omacr;": { "p": [332], "c": "\u014C" }, + "Omega;": { "p": [937], "c": "\u03A9" }, + "Omicron;": { "p": [927], "c": "\u039F" }, + "Oopf;": { "p": [120134], "c": "\uD835\uDD46" }, + "OpenCurlyDoubleQuote;": { "p": [8220], "c": "\u201C" }, + "OpenCurlyQuote;": { "p": [8216], "c": "\u2018" }, + "Or;": { "p": [10836], "c": "\u2A54" }, + "Oscr;": { "p": [119978], "c": "\uD835\uDCAA" }, + "Oslash": { "p": [216], "c": "\u00D8" }, + "Oslash;": { "p": [216], "c": "\u00D8" }, + "Otilde": { "p": [213], "c": "\u00D5" }, + "Otilde;": { "p": [213], "c": "\u00D5" }, + "Otimes;": { "p": [10807], "c": "\u2A37" }, + "Ouml": { "p": [214], "c": "\u00D6" }, + "Ouml;": { "p": [214], "c": "\u00D6" }, + "OverBar;": { "p": [8254], "c": "\u203E" }, + "OverBrace;": { "p": [9182], "c": "\u23DE" }, + "OverBracket;": { "p": [9140], "c": "\u23B4" }, + "OverParenthesis;": { "p": [9180], "c": "\u23DC" }, + "PartialD;": { "p": [8706], "c": "\u2202" }, + "Pcy;": { "p": [1055], "c": "\u041F" }, + "Pfr;": { "p": [120083], "c": "\uD835\uDD13" }, + "Phi;": { "p": [934], "c": "\u03A6" }, + "Pi;": { "p": [928], "c": "\u03A0" }, + "PlusMinus;": { "p": [177], "c": "\u00B1" }, + "Poincareplane;": { "p": [8460], "c": "\u210C" }, + "Popf;": { "p": [8473], "c": "\u2119" }, + "Pr;": { "p": [10939], "c": "\u2ABB" }, + "Precedes;": { "p": [8826], "c": "\u227A" }, + "PrecedesEqual;": { "p": [10927], "c": "\u2AAF" }, + "PrecedesSlantEqual;": { "p": [8828], "c": "\u227C" }, + "PrecedesTilde;": { "p": [8830], "c": "\u227E" }, + "Prime;": { "p": [8243], "c": "\u2033" }, + "Product;": { "p": [8719], "c": "\u220F" }, + "Proportion;": { "p": [8759], "c": "\u2237" }, + "Proportional;": { "p": [8733], "c": "\u221D" }, + "Pscr;": { "p": [119979], "c": "\uD835\uDCAB" }, + "Psi;": { "p": [936], "c": "\u03A8" }, + "QUOT": { "p": [34], "c": "\u0022" }, + "QUOT;": { "p": [34], "c": "\u0022" }, + "Qfr;": { "p": [120084], "c": "\uD835\uDD14" }, + "Qopf;": { "p": [8474], "c": "\u211A" }, + "Qscr;": { "p": [119980], "c": "\uD835\uDCAC" }, + "RBarr;": { "p": [10512], "c": "\u2910" }, + "REG": { "p": [174], "c": "\u00AE" }, + "REG;": { "p": [174], "c": "\u00AE" }, + "Racute;": { "p": [340], "c": "\u0154" }, + "Rang;": { "p": [10219], "c": "\u27EB" }, + "Rarr;": { "p": [8608], "c": "\u21A0" }, + "Rarrtl;": { "p": [10518], "c": "\u2916" }, + "Rcaron;": { "p": [344], "c": "\u0158" }, + "Rcedil;": { "p": [342], "c": "\u0156" }, + "Rcy;": { "p": [1056], "c": "\u0420" }, + "Re;": { "p": [8476], "c": "\u211C" }, + "ReverseElement;": { "p": [8715], "c": "\u220B" }, + "ReverseEquilibrium;": { "p": [8651], "c": "\u21CB" }, + "ReverseUpEquilibrium;": { "p": [10607], "c": "\u296F" }, + "Rfr;": { "p": [8476], "c": "\u211C" }, + "Rho;": { "p": [929], "c": "\u03A1" }, + "RightAngleBracket;": { "p": [10217], "c": "\u27E9" }, + "RightArrow;": { "p": [8594], "c": "\u2192" }, + "RightArrowBar;": { "p": [8677], "c": "\u21E5" }, + "RightArrowLeftArrow;": { "p": [8644], "c": "\u21C4" }, + "RightCeiling;": { "p": [8969], "c": "\u2309" }, + "RightDoubleBracket;": { "p": [10215], "c": "\u27E7" }, + "RightDownTeeVector;": { "p": [10589], "c": "\u295D" }, + "RightDownVector;": { "p": [8642], "c": "\u21C2" }, + "RightDownVectorBar;": { "p": [10581], "c": "\u2955" }, + "RightFloor;": { "p": [8971], "c": "\u230B" }, + "RightTee;": { "p": [8866], "c": "\u22A2" }, + "RightTeeArrow;": { "p": [8614], "c": "\u21A6" }, + "RightTeeVector;": { "p": [10587], "c": "\u295B" }, + "RightTriangle;": { "p": [8883], "c": "\u22B3" }, + "RightTriangleBar;": { "p": [10704], "c": "\u29D0" }, + "RightTriangleEqual;": { "p": [8885], "c": "\u22B5" }, + "RightUpDownVector;": { "p": [10575], "c": "\u294F" }, + "RightUpTeeVector;": { "p": [10588], "c": "\u295C" }, + "RightUpVector;": { "p": [8638], "c": "\u21BE" }, + "RightUpVectorBar;": { "p": [10580], "c": "\u2954" }, + "RightVector;": { "p": [8640], "c": "\u21C0" }, + "RightVectorBar;": { "p": [10579], "c": "\u2953" }, + "Rightarrow;": { "p": [8658], "c": "\u21D2" }, + "Ropf;": { "p": [8477], "c": "\u211D" }, + "RoundImplies;": { "p": [10608], "c": "\u2970" }, + "Rrightarrow;": { "p": [8667], "c": "\u21DB" }, + "Rscr;": { "p": [8475], "c": "\u211B" }, + "Rsh;": { "p": [8625], "c": "\u21B1" }, + "RuleDelayed;": { "p": [10740], "c": "\u29F4" }, + "SHCHcy;": { "p": [1065], "c": "\u0429" }, + "SHcy;": { "p": [1064], "c": "\u0428" }, + "SOFTcy;": { "p": [1068], "c": "\u042C" }, + "Sacute;": { "p": [346], "c": "\u015A" }, + "Sc;": { "p": [10940], "c": "\u2ABC" }, + "Scaron;": { "p": [352], "c": "\u0160" }, + "Scedil;": { "p": [350], "c": "\u015E" }, + "Scirc;": { "p": [348], "c": "\u015C" }, + "Scy;": { "p": [1057], "c": "\u0421" }, + "Sfr;": { "p": [120086], "c": "\uD835\uDD16" }, + "ShortDownArrow;": { "p": [8595], "c": "\u2193" }, + "ShortLeftArrow;": { "p": [8592], "c": "\u2190" }, + "ShortRightArrow;": { "p": [8594], "c": "\u2192" }, + "ShortUpArrow;": { "p": [8593], "c": "\u2191" }, + "Sigma;": { "p": [931], "c": "\u03A3" }, + "SmallCircle;": { "p": [8728], "c": "\u2218" }, + "Sopf;": { "p": [120138], "c": "\uD835\uDD4A" }, + "Sqrt;": { "p": [8730], "c": "\u221A" }, + "Square;": { "p": [9633], "c": "\u25A1" }, + "SquareIntersection;": { "p": [8851], "c": "\u2293" }, + "SquareSubset;": { "p": [8847], "c": "\u228F" }, + "SquareSubsetEqual;": { "p": [8849], "c": "\u2291" }, + "SquareSuperset;": { "p": [8848], "c": "\u2290" }, + "SquareSupersetEqual;": { "p": [8850], "c": "\u2292" }, + "SquareUnion;": { "p": [8852], "c": "\u2294" }, + "Sscr;": { "p": [119982], "c": "\uD835\uDCAE" }, + "Star;": { "p": [8902], "c": "\u22C6" }, + "Sub;": { "p": [8912], "c": "\u22D0" }, + "Subset;": { "p": [8912], "c": "\u22D0" }, + "SubsetEqual;": { "p": [8838], "c": "\u2286" }, + "Succeeds;": { "p": [8827], "c": "\u227B" }, + "SucceedsEqual;": { "p": [10928], "c": "\u2AB0" }, + "SucceedsSlantEqual;": { "p": [8829], "c": "\u227D" }, + "SucceedsTilde;": { "p": [8831], "c": "\u227F" }, + "SuchThat;": { "p": [8715], "c": "\u220B" }, + "Sum;": { "p": [8721], "c": "\u2211" }, + "Sup;": { "p": [8913], "c": "\u22D1" }, + "Superset;": { "p": [8835], "c": "\u2283" }, + "SupersetEqual;": { "p": [8839], "c": "\u2287" }, + "Supset;": { "p": [8913], "c": "\u22D1" }, + "THORN": { "p": [222], "c": "\u00DE" }, + "THORN;": { "p": [222], "c": "\u00DE" }, + "TRADE;": { "p": [8482], "c": "\u2122" }, + "TSHcy;": { "p": [1035], "c": "\u040B" }, + "TScy;": { "p": [1062], "c": "\u0426" }, + "Tab;": { "p": [9], "c": "\u0009" }, + "Tau;": { "p": [932], "c": "\u03A4" }, + "Tcaron;": { "p": [356], "c": "\u0164" }, + "Tcedil;": { "p": [354], "c": "\u0162" }, + "Tcy;": { "p": [1058], "c": "\u0422" }, + "Tfr;": { "p": [120087], "c": "\uD835\uDD17" }, + "Therefore;": { "p": [8756], "c": "\u2234" }, + "Theta;": { "p": [920], "c": "\u0398" }, + "ThickSpace;": { + "p": [8287, 8202], + "c": "\u205F\u200A" + }, + "ThinSpace;": { "p": [8201], "c": "\u2009" }, + "Tilde;": { "p": [8764], "c": "\u223C" }, + "TildeEqual;": { "p": [8771], "c": "\u2243" }, + "TildeFullEqual;": { "p": [8773], "c": "\u2245" }, + "TildeTilde;": { "p": [8776], "c": "\u2248" }, + "Topf;": { "p": [120139], "c": "\uD835\uDD4B" }, + "TripleDot;": { "p": [8411], "c": "\u20DB" }, + "Tscr;": { "p": [119983], "c": "\uD835\uDCAF" }, + "Tstrok;": { "p": [358], "c": "\u0166" }, + "Uacute": { "p": [218], "c": "\u00DA" }, + "Uacute;": { "p": [218], "c": "\u00DA" }, + "Uarr;": { "p": [8607], "c": "\u219F" }, + "Uarrocir;": { "p": [10569], "c": "\u2949" }, + "Ubrcy;": { "p": [1038], "c": "\u040E" }, + "Ubreve;": { "p": [364], "c": "\u016C" }, + "Ucirc": { "p": [219], "c": "\u00DB" }, + "Ucirc;": { "p": [219], "c": "\u00DB" }, + "Ucy;": { "p": [1059], "c": "\u0423" }, + "Udblac;": { "p": [368], "c": "\u0170" }, + "Ufr;": { "p": [120088], "c": "\uD835\uDD18" }, + "Ugrave": { "p": [217], "c": "\u00D9" }, + "Ugrave;": { "p": [217], "c": "\u00D9" }, + "Umacr;": { "p": [362], "c": "\u016A" }, + "UnderBar;": { "p": [95], "c": "\u005F" }, + "UnderBrace;": { "p": [9183], "c": "\u23DF" }, + "UnderBracket;": { "p": [9141], "c": "\u23B5" }, + "UnderParenthesis;": { "p": [9181], "c": "\u23DD" }, + "Union;": { "p": [8899], "c": "\u22C3" }, + "UnionPlus;": { "p": [8846], "c": "\u228E" }, + "Uogon;": { "p": [370], "c": "\u0172" }, + "Uopf;": { "p": [120140], "c": "\uD835\uDD4C" }, + "UpArrow;": { "p": [8593], "c": "\u2191" }, + "UpArrowBar;": { "p": [10514], "c": "\u2912" }, + "UpArrowDownArrow;": { "p": [8645], "c": "\u21C5" }, + "UpDownArrow;": { "p": [8597], "c": "\u2195" }, + "UpEquilibrium;": { "p": [10606], "c": "\u296E" }, + "UpTee;": { "p": [8869], "c": "\u22A5" }, + "UpTeeArrow;": { "p": [8613], "c": "\u21A5" }, + "Uparrow;": { "p": [8657], "c": "\u21D1" }, + "Updownarrow;": { "p": [8661], "c": "\u21D5" }, + "UpperLeftArrow;": { "p": [8598], "c": "\u2196" }, + "UpperRightArrow;": { "p": [8599], "c": "\u2197" }, + "Upsi;": { "p": [978], "c": "\u03D2" }, + "Upsilon;": { "p": [933], "c": "\u03A5" }, + "Uring;": { "p": [366], "c": "\u016E" }, + "Uscr;": { "p": [119984], "c": "\uD835\uDCB0" }, + "Utilde;": { "p": [360], "c": "\u0168" }, + "Uuml": { "p": [220], "c": "\u00DC" }, + "Uuml;": { "p": [220], "c": "\u00DC" }, + "VDash;": { "p": [8875], "c": "\u22AB" }, + "Vbar;": { "p": [10987], "c": "\u2AEB" }, + "Vcy;": { "p": [1042], "c": "\u0412" }, + "Vdash;": { "p": [8873], "c": "\u22A9" }, + "Vdashl;": { "p": [10982], "c": "\u2AE6" }, + "Vee;": { "p": [8897], "c": "\u22C1" }, + "Verbar;": { "p": [8214], "c": "\u2016" }, + "Vert;": { "p": [8214], "c": "\u2016" }, + "VerticalBar;": { "p": [8739], "c": "\u2223" }, + "VerticalLine;": { "p": [124], "c": "\u007C" }, + "VerticalSeparator;": { "p": [10072], "c": "\u2758" }, + "VerticalTilde;": { "p": [8768], "c": "\u2240" }, + "VeryThinSpace;": { "p": [8202], "c": "\u200A" }, + "Vfr;": { "p": [120089], "c": "\uD835\uDD19" }, + "Vopf;": { "p": [120141], "c": "\uD835\uDD4D" }, + "Vscr;": { "p": [119985], "c": "\uD835\uDCB1" }, + "Vvdash;": { "p": [8874], "c": "\u22AA" }, + "Wcirc;": { "p": [372], "c": "\u0174" }, + "Wedge;": { "p": [8896], "c": "\u22C0" }, + "Wfr;": { "p": [120090], "c": "\uD835\uDD1A" }, + "Wopf;": { "p": [120142], "c": "\uD835\uDD4E" }, + "Wscr;": { "p": [119986], "c": "\uD835\uDCB2" }, + "Xfr;": { "p": [120091], "c": "\uD835\uDD1B" }, + "Xi;": { "p": [926], "c": "\u039E" }, + "Xopf;": { "p": [120143], "c": "\uD835\uDD4F" }, + "Xscr;": { "p": [119987], "c": "\uD835\uDCB3" }, + "YAcy;": { "p": [1071], "c": "\u042F" }, + "YIcy;": { "p": [1031], "c": "\u0407" }, + "YUcy;": { "p": [1070], "c": "\u042E" }, + "Yacute": { "p": [221], "c": "\u00DD" }, + "Yacute;": { "p": [221], "c": "\u00DD" }, + "Ycirc;": { "p": [374], "c": "\u0176" }, + "Ycy;": { "p": [1067], "c": "\u042B" }, + "Yfr;": { "p": [120092], "c": "\uD835\uDD1C" }, + "Yopf;": { "p": [120144], "c": "\uD835\uDD50" }, + "Yscr;": { "p": [119988], "c": "\uD835\uDCB4" }, + "Yuml;": { "p": [376], "c": "\u0178" }, + "ZHcy;": { "p": [1046], "c": "\u0416" }, + "Zacute;": { "p": [377], "c": "\u0179" }, + "Zcaron;": { "p": [381], "c": "\u017D" }, + "Zcy;": { "p": [1047], "c": "\u0417" }, + "Zdot;": { "p": [379], "c": "\u017B" }, + "ZeroWidthSpace;": { "p": [8203], "c": "\u200B" }, + "Zeta;": { "p": [918], "c": "\u0396" }, + "Zfr;": { "p": [8488], "c": "\u2128" }, + "Zopf;": { "p": [8484], "c": "\u2124" }, + "Zscr;": { "p": [119989], "c": "\uD835\uDCB5" }, + "aacute": { "p": [225], "c": "\u00E1" }, + "aacute;": { "p": [225], "c": "\u00E1" }, + "abreve;": { "p": [259], "c": "\u0103" }, + "ac;": { "p": [8766], "c": "\u223E" }, + "acE;": { "p": [8766, 819], "c": "\u223E\u0333" }, + "acd;": { "p": [8767], "c": "\u223F" }, + "acirc": { "p": [226], "c": "\u00E2" }, + "acirc;": { "p": [226], "c": "\u00E2" }, + "acute": { "p": [180], "c": "\u00B4" }, + "acute;": { "p": [180], "c": "\u00B4" }, + "acy;": { "p": [1072], "c": "\u0430" }, + "aelig": { "p": [230], "c": "\u00E6" }, + "aelig;": { "p": [230], "c": "\u00E6" }, + "af;": { "p": [8289], "c": "\u2061" }, + "afr;": { "p": [120094], "c": "\uD835\uDD1E" }, + "agrave": { "p": [224], "c": "\u00E0" }, + "agrave;": { "p": [224], "c": "\u00E0" }, + "alefsym;": { "p": [8501], "c": "\u2135" }, + "aleph;": { "p": [8501], "c": "\u2135" }, + "alpha;": { "p": [945], "c": "\u03B1" }, + "amacr;": { "p": [257], "c": "\u0101" }, + "amalg;": { "p": [10815], "c": "\u2A3F" }, + "amp": { "p": [38], "c": "\u0026" }, + "amp;": { "p": [38], "c": "\u0026" }, + "and;": { "p": [8743], "c": "\u2227" }, + "andand;": { "p": [10837], "c": "\u2A55" }, + "andd;": { "p": [10844], "c": "\u2A5C" }, + "andslope;": { "p": [10840], "c": "\u2A58" }, + "andv;": { "p": [10842], "c": "\u2A5A" }, + "ang;": { "p": [8736], "c": "\u2220" }, + "ange;": { "p": [10660], "c": "\u29A4" }, + "angle;": { "p": [8736], "c": "\u2220" }, + "angmsd;": { "p": [8737], "c": "\u2221" }, + "angmsdaa;": { "p": [10664], "c": "\u29A8" }, + "angmsdab;": { "p": [10665], "c": "\u29A9" }, + "angmsdac;": { "p": [10666], "c": "\u29AA" }, + "angmsdad;": { "p": [10667], "c": "\u29AB" }, + "angmsdae;": { "p": [10668], "c": "\u29AC" }, + "angmsdaf;": { "p": [10669], "c": "\u29AD" }, + "angmsdag;": { "p": [10670], "c": "\u29AE" }, + "angmsdah;": { "p": [10671], "c": "\u29AF" }, + "angrt;": { "p": [8735], "c": "\u221F" }, + "angrtvb;": { "p": [8894], "c": "\u22BE" }, + "angrtvbd;": { "p": [10653], "c": "\u299D" }, + "angsph;": { "p": [8738], "c": "\u2222" }, + "angst;": { "p": [197], "c": "\u00C5" }, + "angzarr;": { "p": [9084], "c": "\u237C" }, + "aogon;": { "p": [261], "c": "\u0105" }, + "aopf;": { "p": [120146], "c": "\uD835\uDD52" }, + "ap;": { "p": [8776], "c": "\u2248" }, + "apE;": { "p": [10864], "c": "\u2A70" }, + "apacir;": { "p": [10863], "c": "\u2A6F" }, + "ape;": { "p": [8778], "c": "\u224A" }, + "apid;": { "p": [8779], "c": "\u224B" }, + "apos;": { "p": [39], "c": "\u0027" }, + "approx;": { "p": [8776], "c": "\u2248" }, + "approxeq;": { "p": [8778], "c": "\u224A" }, + "aring": { "p": [229], "c": "\u00E5" }, + "aring;": { "p": [229], "c": "\u00E5" }, + "ascr;": { "p": [119990], "c": "\uD835\uDCB6" }, + "ast;": { "p": [42], "c": "\u002A" }, + "asymp;": { "p": [8776], "c": "\u2248" }, + "asympeq;": { "p": [8781], "c": "\u224D" }, + "atilde": { "p": [227], "c": "\u00E3" }, + "atilde;": { "p": [227], "c": "\u00E3" }, + "auml": { "p": [228], "c": "\u00E4" }, + "auml;": { "p": [228], "c": "\u00E4" }, + "awconint;": { "p": [8755], "c": "\u2233" }, + "awint;": { "p": [10769], "c": "\u2A11" }, + "bNot;": { "p": [10989], "c": "\u2AED" }, + "backcong;": { "p": [8780], "c": "\u224C" }, + "backepsilon;": { "p": [1014], "c": "\u03F6" }, + "backprime;": { "p": [8245], "c": "\u2035" }, + "backsim;": { "p": [8765], "c": "\u223D" }, + "backsimeq;": { "p": [8909], "c": "\u22CD" }, + "barvee;": { "p": [8893], "c": "\u22BD" }, + "barwed;": { "p": [8965], "c": "\u2305" }, + "barwedge;": { "p": [8965], "c": "\u2305" }, + "bbrk;": { "p": [9141], "c": "\u23B5" }, + "bbrktbrk;": { "p": [9142], "c": "\u23B6" }, + "bcong;": { "p": [8780], "c": "\u224C" }, + "bcy;": { "p": [1073], "c": "\u0431" }, + "bdquo;": { "p": [8222], "c": "\u201E" }, + "becaus;": { "p": [8757], "c": "\u2235" }, + "because;": { "p": [8757], "c": "\u2235" }, + "bemptyv;": { "p": [10672], "c": "\u29B0" }, + "bepsi;": { "p": [1014], "c": "\u03F6" }, + "bernou;": { "p": [8492], "c": "\u212C" }, + "beta;": { "p": [946], "c": "\u03B2" }, + "beth;": { "p": [8502], "c": "\u2136" }, + "between;": { "p": [8812], "c": "\u226C" }, + "bfr;": { "p": [120095], "c": "\uD835\uDD1F" }, + "bigcap;": { "p": [8898], "c": "\u22C2" }, + "bigcirc;": { "p": [9711], "c": "\u25EF" }, + "bigcup;": { "p": [8899], "c": "\u22C3" }, + "bigodot;": { "p": [10752], "c": "\u2A00" }, + "bigoplus;": { "p": [10753], "c": "\u2A01" }, + "bigotimes;": { "p": [10754], "c": "\u2A02" }, + "bigsqcup;": { "p": [10758], "c": "\u2A06" }, + "bigstar;": { "p": [9733], "c": "\u2605" }, + "bigtriangledown;": { "p": [9661], "c": "\u25BD" }, + "bigtriangleup;": { "p": [9651], "c": "\u25B3" }, + "biguplus;": { "p": [10756], "c": "\u2A04" }, + "bigvee;": { "p": [8897], "c": "\u22C1" }, + "bigwedge;": { "p": [8896], "c": "\u22C0" }, + "bkarow;": { "p": [10509], "c": "\u290D" }, + "blacklozenge;": { "p": [10731], "c": "\u29EB" }, + "blacksquare;": { "p": [9642], "c": "\u25AA" }, + "blacktriangle;": { "p": [9652], "c": "\u25B4" }, + "blacktriangledown;": { "p": [9662], "c": "\u25BE" }, + "blacktriangleleft;": { "p": [9666], "c": "\u25C2" }, + "blacktriangleright;": { "p": [9656], "c": "\u25B8" }, + "blank;": { "p": [9251], "c": "\u2423" }, + "blk12;": { "p": [9618], "c": "\u2592" }, + "blk14;": { "p": [9617], "c": "\u2591" }, + "blk34;": { "p": [9619], "c": "\u2593" }, + "block;": { "p": [9608], "c": "\u2588" }, + "bne;": { "p": [61, 8421], "c": "\u003D\u20E5" }, + "bnequiv;": { "p": [8801, 8421], "c": "\u2261\u20E5" }, + "bnot;": { "p": [8976], "c": "\u2310" }, + "bopf;": { "p": [120147], "c": "\uD835\uDD53" }, + "bot;": { "p": [8869], "c": "\u22A5" }, + "bottom;": { "p": [8869], "c": "\u22A5" }, + "bowtie;": { "p": [8904], "c": "\u22C8" }, + "boxDL;": { "p": [9559], "c": "\u2557" }, + "boxDR;": { "p": [9556], "c": "\u2554" }, + "boxDl;": { "p": [9558], "c": "\u2556" }, + "boxDr;": { "p": [9555], "c": "\u2553" }, + "boxH;": { "p": [9552], "c": "\u2550" }, + "boxHD;": { "p": [9574], "c": "\u2566" }, + "boxHU;": { "p": [9577], "c": "\u2569" }, + "boxHd;": { "p": [9572], "c": "\u2564" }, + "boxHu;": { "p": [9575], "c": "\u2567" }, + "boxUL;": { "p": [9565], "c": "\u255D" }, + "boxUR;": { "p": [9562], "c": "\u255A" }, + "boxUl;": { "p": [9564], "c": "\u255C" }, + "boxUr;": { "p": [9561], "c": "\u2559" }, + "boxV;": { "p": [9553], "c": "\u2551" }, + "boxVH;": { "p": [9580], "c": "\u256C" }, + "boxVL;": { "p": [9571], "c": "\u2563" }, + "boxVR;": { "p": [9568], "c": "\u2560" }, + "boxVh;": { "p": [9579], "c": "\u256B" }, + "boxVl;": { "p": [9570], "c": "\u2562" }, + "boxVr;": { "p": [9567], "c": "\u255F" }, + "boxbox;": { "p": [10697], "c": "\u29C9" }, + "boxdL;": { "p": [9557], "c": "\u2555" }, + "boxdR;": { "p": [9554], "c": "\u2552" }, + "boxdl;": { "p": [9488], "c": "\u2510" }, + "boxdr;": { "p": [9484], "c": "\u250C" }, + "boxh;": { "p": [9472], "c": "\u2500" }, + "boxhD;": { "p": [9573], "c": "\u2565" }, + "boxhU;": { "p": [9576], "c": "\u2568" }, + "boxhd;": { "p": [9516], "c": "\u252C" }, + "boxhu;": { "p": [9524], "c": "\u2534" }, + "boxminus;": { "p": [8863], "c": "\u229F" }, + "boxplus;": { "p": [8862], "c": "\u229E" }, + "boxtimes;": { "p": [8864], "c": "\u22A0" }, + "boxuL;": { "p": [9563], "c": "\u255B" }, + "boxuR;": { "p": [9560], "c": "\u2558" }, + "boxul;": { "p": [9496], "c": "\u2518" }, + "boxur;": { "p": [9492], "c": "\u2514" }, + "boxv;": { "p": [9474], "c": "\u2502" }, + "boxvH;": { "p": [9578], "c": "\u256A" }, + "boxvL;": { "p": [9569], "c": "\u2561" }, + "boxvR;": { "p": [9566], "c": "\u255E" }, + "boxvh;": { "p": [9532], "c": "\u253C" }, + "boxvl;": { "p": [9508], "c": "\u2524" }, + "boxvr;": { "p": [9500], "c": "\u251C" }, + "bprime;": { "p": [8245], "c": "\u2035" }, + "breve;": { "p": [728], "c": "\u02D8" }, + "brvbar": { "p": [166], "c": "\u00A6" }, + "brvbar;": { "p": [166], "c": "\u00A6" }, + "bscr;": { "p": [119991], "c": "\uD835\uDCB7" }, + "bsemi;": { "p": [8271], "c": "\u204F" }, + "bsim;": { "p": [8765], "c": "\u223D" }, + "bsime;": { "p": [8909], "c": "\u22CD" }, + "bsol;": { "p": [92], "c": "\u005C" }, + "bsolb;": { "p": [10693], "c": "\u29C5" }, + "bsolhsub;": { "p": [10184], "c": "\u27C8" }, + "bull;": { "p": [8226], "c": "\u2022" }, + "bullet;": { "p": [8226], "c": "\u2022" }, + "bump;": { "p": [8782], "c": "\u224E" }, + "bumpE;": { "p": [10926], "c": "\u2AAE" }, + "bumpe;": { "p": [8783], "c": "\u224F" }, + "bumpeq;": { "p": [8783], "c": "\u224F" }, + "cacute;": { "p": [263], "c": "\u0107" }, + "cap;": { "p": [8745], "c": "\u2229" }, + "capand;": { "p": [10820], "c": "\u2A44" }, + "capbrcup;": { "p": [10825], "c": "\u2A49" }, + "capcap;": { "p": [10827], "c": "\u2A4B" }, + "capcup;": { "p": [10823], "c": "\u2A47" }, + "capdot;": { "p": [10816], "c": "\u2A40" }, + "caps;": { "p": [8745, 65024], "c": "\u2229\uFE00" }, + "caret;": { "p": [8257], "c": "\u2041" }, + "caron;": { "p": [711], "c": "\u02C7" }, + "ccaps;": { "p": [10829], "c": "\u2A4D" }, + "ccaron;": { "p": [269], "c": "\u010D" }, + "ccedil": { "p": [231], "c": "\u00E7" }, + "ccedil;": { "p": [231], "c": "\u00E7" }, + "ccirc;": { "p": [265], "c": "\u0109" }, + "ccups;": { "p": [10828], "c": "\u2A4C" }, + "ccupssm;": { "p": [10832], "c": "\u2A50" }, + "cdot;": { "p": [267], "c": "\u010B" }, + "cedil": { "p": [184], "c": "\u00B8" }, + "cedil;": { "p": [184], "c": "\u00B8" }, + "cemptyv;": { "p": [10674], "c": "\u29B2" }, + "cent": { "p": [162], "c": "\u00A2" }, + "cent;": { "p": [162], "c": "\u00A2" }, + "centerdot;": { "p": [183], "c": "\u00B7" }, + "cfr;": { "p": [120096], "c": "\uD835\uDD20" }, + "chcy;": { "p": [1095], "c": "\u0447" }, + "check;": { "p": [10003], "c": "\u2713" }, + "checkmark;": { "p": [10003], "c": "\u2713" }, + "chi;": { "p": [967], "c": "\u03C7" }, + "cir;": { "p": [9675], "c": "\u25CB" }, + "cirE;": { "p": [10691], "c": "\u29C3" }, + "circ;": { "p": [710], "c": "\u02C6" }, + "circeq;": { "p": [8791], "c": "\u2257" }, + "circlearrowleft;": { "p": [8634], "c": "\u21BA" }, + "circlearrowright;": { "p": [8635], "c": "\u21BB" }, + "circledR;": { "p": [174], "c": "\u00AE" }, + "circledS;": { "p": [9416], "c": "\u24C8" }, + "circledast;": { "p": [8859], "c": "\u229B" }, + "circledcirc;": { "p": [8858], "c": "\u229A" }, + "circleddash;": { "p": [8861], "c": "\u229D" }, + "cire;": { "p": [8791], "c": "\u2257" }, + "cirfnint;": { "p": [10768], "c": "\u2A10" }, + "cirmid;": { "p": [10991], "c": "\u2AEF" }, + "cirscir;": { "p": [10690], "c": "\u29C2" }, + "clubs;": { "p": [9827], "c": "\u2663" }, + "clubsuit;": { "p": [9827], "c": "\u2663" }, + "colon;": { "p": [58], "c": "\u003A" }, + "colone;": { "p": [8788], "c": "\u2254" }, + "coloneq;": { "p": [8788], "c": "\u2254" }, + "comma;": { "p": [44], "c": "\u002C" }, + "commat;": { "p": [64], "c": "\u0040" }, + "comp;": { "p": [8705], "c": "\u2201" }, + "compfn;": { "p": [8728], "c": "\u2218" }, + "complement;": { "p": [8705], "c": "\u2201" }, + "complexes;": { "p": [8450], "c": "\u2102" }, + "cong;": { "p": [8773], "c": "\u2245" }, + "congdot;": { "p": [10861], "c": "\u2A6D" }, + "conint;": { "p": [8750], "c": "\u222E" }, + "copf;": { "p": [120148], "c": "\uD835\uDD54" }, + "coprod;": { "p": [8720], "c": "\u2210" }, + "copy": { "p": [169], "c": "\u00A9" }, + "copy;": { "p": [169], "c": "\u00A9" }, + "copysr;": { "p": [8471], "c": "\u2117" }, + "crarr;": { "p": [8629], "c": "\u21B5" }, + "cross;": { "p": [10007], "c": "\u2717" }, + "cscr;": { "p": [119992], "c": "\uD835\uDCB8" }, + "csub;": { "p": [10959], "c": "\u2ACF" }, + "csube;": { "p": [10961], "c": "\u2AD1" }, + "csup;": { "p": [10960], "c": "\u2AD0" }, + "csupe;": { "p": [10962], "c": "\u2AD2" }, + "ctdot;": { "p": [8943], "c": "\u22EF" }, + "cudarrl;": { "p": [10552], "c": "\u2938" }, + "cudarrr;": { "p": [10549], "c": "\u2935" }, + "cuepr;": { "p": [8926], "c": "\u22DE" }, + "cuesc;": { "p": [8927], "c": "\u22DF" }, + "cularr;": { "p": [8630], "c": "\u21B6" }, + "cularrp;": { "p": [10557], "c": "\u293D" }, + "cup;": { "p": [8746], "c": "\u222A" }, + "cupbrcap;": { "p": [10824], "c": "\u2A48" }, + "cupcap;": { "p": [10822], "c": "\u2A46" }, + "cupcup;": { "p": [10826], "c": "\u2A4A" }, + "cupdot;": { "p": [8845], "c": "\u228D" }, + "cupor;": { "p": [10821], "c": "\u2A45" }, + "cups;": { "p": [8746, 65024], "c": "\u222A\uFE00" }, + "curarr;": { "p": [8631], "c": "\u21B7" }, + "curarrm;": { "p": [10556], "c": "\u293C" }, + "curlyeqprec;": { "p": [8926], "c": "\u22DE" }, + "curlyeqsucc;": { "p": [8927], "c": "\u22DF" }, + "curlyvee;": { "p": [8910], "c": "\u22CE" }, + "curlywedge;": { "p": [8911], "c": "\u22CF" }, + "curren": { "p": [164], "c": "\u00A4" }, + "curren;": { "p": [164], "c": "\u00A4" }, + "curvearrowleft;": { "p": [8630], "c": "\u21B6" }, + "curvearrowright;": { "p": [8631], "c": "\u21B7" }, + "cuvee;": { "p": [8910], "c": "\u22CE" }, + "cuwed;": { "p": [8911], "c": "\u22CF" }, + "cwconint;": { "p": [8754], "c": "\u2232" }, + "cwint;": { "p": [8753], "c": "\u2231" }, + "cylcty;": { "p": [9005], "c": "\u232D" }, + "dArr;": { "p": [8659], "c": "\u21D3" }, + "dHar;": { "p": [10597], "c": "\u2965" }, + "dagger;": { "p": [8224], "c": "\u2020" }, + "daleth;": { "p": [8504], "c": "\u2138" }, + "darr;": { "p": [8595], "c": "\u2193" }, + "dash;": { "p": [8208], "c": "\u2010" }, + "dashv;": { "p": [8867], "c": "\u22A3" }, + "dbkarow;": { "p": [10511], "c": "\u290F" }, + "dblac;": { "p": [733], "c": "\u02DD" }, + "dcaron;": { "p": [271], "c": "\u010F" }, + "dcy;": { "p": [1076], "c": "\u0434" }, + "dd;": { "p": [8518], "c": "\u2146" }, + "ddagger;": { "p": [8225], "c": "\u2021" }, + "ddarr;": { "p": [8650], "c": "\u21CA" }, + "ddotseq;": { "p": [10871], "c": "\u2A77" }, + "deg": { "p": [176], "c": "\u00B0" }, + "deg;": { "p": [176], "c": "\u00B0" }, + "delta;": { "p": [948], "c": "\u03B4" }, + "demptyv;": { "p": [10673], "c": "\u29B1" }, + "dfisht;": { "p": [10623], "c": "\u297F" }, + "dfr;": { "p": [120097], "c": "\uD835\uDD21" }, + "dharl;": { "p": [8643], "c": "\u21C3" }, + "dharr;": { "p": [8642], "c": "\u21C2" }, + "diam;": { "p": [8900], "c": "\u22C4" }, + "diamond;": { "p": [8900], "c": "\u22C4" }, + "diamondsuit;": { "p": [9830], "c": "\u2666" }, + "diams;": { "p": [9830], "c": "\u2666" }, + "die;": { "p": [168], "c": "\u00A8" }, + "digamma;": { "p": [989], "c": "\u03DD" }, + "disin;": { "p": [8946], "c": "\u22F2" }, + "div;": { "p": [247], "c": "\u00F7" }, + "divide": { "p": [247], "c": "\u00F7" }, + "divide;": { "p": [247], "c": "\u00F7" }, + "divideontimes;": { "p": [8903], "c": "\u22C7" }, + "divonx;": { "p": [8903], "c": "\u22C7" }, + "djcy;": { "p": [1106], "c": "\u0452" }, + "dlcorn;": { "p": [8990], "c": "\u231E" }, + "dlcrop;": { "p": [8973], "c": "\u230D" }, + "dollar;": { "p": [36], "c": "\u0024" }, + "dopf;": { "p": [120149], "c": "\uD835\uDD55" }, + "dot;": { "p": [729], "c": "\u02D9" }, + "doteq;": { "p": [8784], "c": "\u2250" }, + "doteqdot;": { "p": [8785], "c": "\u2251" }, + "dotminus;": { "p": [8760], "c": "\u2238" }, + "dotplus;": { "p": [8724], "c": "\u2214" }, + "dotsquare;": { "p": [8865], "c": "\u22A1" }, + "doublebarwedge;": { "p": [8966], "c": "\u2306" }, + "downarrow;": { "p": [8595], "c": "\u2193" }, + "downdownarrows;": { "p": [8650], "c": "\u21CA" }, + "downharpoonleft;": { "p": [8643], "c": "\u21C3" }, + "downharpoonright;": { "p": [8642], "c": "\u21C2" }, + "drbkarow;": { "p": [10512], "c": "\u2910" }, + "drcorn;": { "p": [8991], "c": "\u231F" }, + "drcrop;": { "p": [8972], "c": "\u230C" }, + "dscr;": { "p": [119993], "c": "\uD835\uDCB9" }, + "dscy;": { "p": [1109], "c": "\u0455" }, + "dsol;": { "p": [10742], "c": "\u29F6" }, + "dstrok;": { "p": [273], "c": "\u0111" }, + "dtdot;": { "p": [8945], "c": "\u22F1" }, + "dtri;": { "p": [9663], "c": "\u25BF" }, + "dtrif;": { "p": [9662], "c": "\u25BE" }, + "duarr;": { "p": [8693], "c": "\u21F5" }, + "duhar;": { "p": [10607], "c": "\u296F" }, + "dwangle;": { "p": [10662], "c": "\u29A6" }, + "dzcy;": { "p": [1119], "c": "\u045F" }, + "dzigrarr;": { "p": [10239], "c": "\u27FF" }, + "eDDot;": { "p": [10871], "c": "\u2A77" }, + "eDot;": { "p": [8785], "c": "\u2251" }, + "eacute": { "p": [233], "c": "\u00E9" }, + "eacute;": { "p": [233], "c": "\u00E9" }, + "easter;": { "p": [10862], "c": "\u2A6E" }, + "ecaron;": { "p": [283], "c": "\u011B" }, + "ecir;": { "p": [8790], "c": "\u2256" }, + "ecirc": { "p": [234], "c": "\u00EA" }, + "ecirc;": { "p": [234], "c": "\u00EA" }, + "ecolon;": { "p": [8789], "c": "\u2255" }, + "ecy;": { "p": [1101], "c": "\u044D" }, + "edot;": { "p": [279], "c": "\u0117" }, + "ee;": { "p": [8519], "c": "\u2147" }, + "efDot;": { "p": [8786], "c": "\u2252" }, + "efr;": { "p": [120098], "c": "\uD835\uDD22" }, + "eg;": { "p": [10906], "c": "\u2A9A" }, + "egrave": { "p": [232], "c": "\u00E8" }, + "egrave;": { "p": [232], "c": "\u00E8" }, + "egs;": { "p": [10902], "c": "\u2A96" }, + "egsdot;": { "p": [10904], "c": "\u2A98" }, + "el;": { "p": [10905], "c": "\u2A99" }, + "elinters;": { "p": [9191], "c": "\u23E7" }, + "ell;": { "p": [8467], "c": "\u2113" }, + "els;": { "p": [10901], "c": "\u2A95" }, + "elsdot;": { "p": [10903], "c": "\u2A97" }, + "emacr;": { "p": [275], "c": "\u0113" }, + "empty;": { "p": [8709], "c": "\u2205" }, + "emptyset;": { "p": [8709], "c": "\u2205" }, + "emptyv;": { "p": [8709], "c": "\u2205" }, + "emsp13;": { "p": [8196], "c": "\u2004" }, + "emsp14;": { "p": [8197], "c": "\u2005" }, + "emsp;": { "p": [8195], "c": "\u2003" }, + "eng;": { "p": [331], "c": "\u014B" }, + "ensp;": { "p": [8194], "c": "\u2002" }, + "eogon;": { "p": [281], "c": "\u0119" }, + "eopf;": { "p": [120150], "c": "\uD835\uDD56" }, + "epar;": { "p": [8917], "c": "\u22D5" }, + "eparsl;": { "p": [10723], "c": "\u29E3" }, + "eplus;": { "p": [10865], "c": "\u2A71" }, + "epsi;": { "p": [949], "c": "\u03B5" }, + "epsilon;": { "p": [949], "c": "\u03B5" }, + "epsiv;": { "p": [1013], "c": "\u03F5" }, + "eqcirc;": { "p": [8790], "c": "\u2256" }, + "eqcolon;": { "p": [8789], "c": "\u2255" }, + "eqsim;": { "p": [8770], "c": "\u2242" }, + "eqslantgtr;": { "p": [10902], "c": "\u2A96" }, + "eqslantless;": { "p": [10901], "c": "\u2A95" }, + "equals;": { "p": [61], "c": "\u003D" }, + "equest;": { "p": [8799], "c": "\u225F" }, + "equiv;": { "p": [8801], "c": "\u2261" }, + "equivDD;": { "p": [10872], "c": "\u2A78" }, + "eqvparsl;": { "p": [10725], "c": "\u29E5" }, + "erDot;": { "p": [8787], "c": "\u2253" }, + "erarr;": { "p": [10609], "c": "\u2971" }, + "escr;": { "p": [8495], "c": "\u212F" }, + "esdot;": { "p": [8784], "c": "\u2250" }, + "esim;": { "p": [8770], "c": "\u2242" }, + "eta;": { "p": [951], "c": "\u03B7" }, + "eth": { "p": [240], "c": "\u00F0" }, + "eth;": { "p": [240], "c": "\u00F0" }, + "euml": { "p": [235], "c": "\u00EB" }, + "euml;": { "p": [235], "c": "\u00EB" }, + "euro;": { "p": [8364], "c": "\u20AC" }, + "excl;": { "p": [33], "c": "\u0021" }, + "exist;": { "p": [8707], "c": "\u2203" }, + "expectation;": { "p": [8496], "c": "\u2130" }, + "exponentiale;": { "p": [8519], "c": "\u2147" }, + "fallingdotseq;": { "p": [8786], "c": "\u2252" }, + "fcy;": { "p": [1092], "c": "\u0444" }, + "female;": { "p": [9792], "c": "\u2640" }, + "ffilig;": { "p": [64259], "c": "\uFB03" }, + "fflig;": { "p": [64256], "c": "\uFB00" }, + "ffllig;": { "p": [64260], "c": "\uFB04" }, + "ffr;": { "p": [120099], "c": "\uD835\uDD23" }, + "filig;": { "p": [64257], "c": "\uFB01" }, + "fjlig;": { "p": [102, 106], "c": "\u0066\u006A" }, + "flat;": { "p": [9837], "c": "\u266D" }, + "fllig;": { "p": [64258], "c": "\uFB02" }, + "fltns;": { "p": [9649], "c": "\u25B1" }, + "fnof;": { "p": [402], "c": "\u0192" }, + "fopf;": { "p": [120151], "c": "\uD835\uDD57" }, + "forall;": { "p": [8704], "c": "\u2200" }, + "fork;": { "p": [8916], "c": "\u22D4" }, + "forkv;": { "p": [10969], "c": "\u2AD9" }, + "fpartint;": { "p": [10765], "c": "\u2A0D" }, + "frac12": { "p": [189], "c": "\u00BD" }, + "frac12;": { "p": [189], "c": "\u00BD" }, + "frac13;": { "p": [8531], "c": "\u2153" }, + "frac14": { "p": [188], "c": "\u00BC" }, + "frac14;": { "p": [188], "c": "\u00BC" }, + "frac15;": { "p": [8533], "c": "\u2155" }, + "frac16;": { "p": [8537], "c": "\u2159" }, + "frac18;": { "p": [8539], "c": "\u215B" }, + "frac23;": { "p": [8532], "c": "\u2154" }, + "frac25;": { "p": [8534], "c": "\u2156" }, + "frac34": { "p": [190], "c": "\u00BE" }, + "frac34;": { "p": [190], "c": "\u00BE" }, + "frac35;": { "p": [8535], "c": "\u2157" }, + "frac38;": { "p": [8540], "c": "\u215C" }, + "frac45;": { "p": [8536], "c": "\u2158" }, + "frac56;": { "p": [8538], "c": "\u215A" }, + "frac58;": { "p": [8541], "c": "\u215D" }, + "frac78;": { "p": [8542], "c": "\u215E" }, + "frasl;": { "p": [8260], "c": "\u2044" }, + "frown;": { "p": [8994], "c": "\u2322" }, + "fscr;": { "p": [119995], "c": "\uD835\uDCBB" }, + "gE;": { "p": [8807], "c": "\u2267" }, + "gEl;": { "p": [10892], "c": "\u2A8C" }, + "gacute;": { "p": [501], "c": "\u01F5" }, + "gamma;": { "p": [947], "c": "\u03B3" }, + "gammad;": { "p": [989], "c": "\u03DD" }, + "gap;": { "p": [10886], "c": "\u2A86" }, + "gbreve;": { "p": [287], "c": "\u011F" }, + "gcirc;": { "p": [285], "c": "\u011D" }, + "gcy;": { "p": [1075], "c": "\u0433" }, + "gdot;": { "p": [289], "c": "\u0121" }, + "ge;": { "p": [8805], "c": "\u2265" }, + "gel;": { "p": [8923], "c": "\u22DB" }, + "geq;": { "p": [8805], "c": "\u2265" }, + "geqq;": { "p": [8807], "c": "\u2267" }, + "geqslant;": { "p": [10878], "c": "\u2A7E" }, + "ges;": { "p": [10878], "c": "\u2A7E" }, + "gescc;": { "p": [10921], "c": "\u2AA9" }, + "gesdot;": { "p": [10880], "c": "\u2A80" }, + "gesdoto;": { "p": [10882], "c": "\u2A82" }, + "gesdotol;": { "p": [10884], "c": "\u2A84" }, + "gesl;": { "p": [8923, 65024], "c": "\u22DB\uFE00" }, + "gesles;": { "p": [10900], "c": "\u2A94" }, + "gfr;": { "p": [120100], "c": "\uD835\uDD24" }, + "gg;": { "p": [8811], "c": "\u226B" }, + "ggg;": { "p": [8921], "c": "\u22D9" }, + "gimel;": { "p": [8503], "c": "\u2137" }, + "gjcy;": { "p": [1107], "c": "\u0453" }, + "gl;": { "p": [8823], "c": "\u2277" }, + "glE;": { "p": [10898], "c": "\u2A92" }, + "gla;": { "p": [10917], "c": "\u2AA5" }, + "glj;": { "p": [10916], "c": "\u2AA4" }, + "gnE;": { "p": [8809], "c": "\u2269" }, + "gnap;": { "p": [10890], "c": "\u2A8A" }, + "gnapprox;": { "p": [10890], "c": "\u2A8A" }, + "gne;": { "p": [10888], "c": "\u2A88" }, + "gneq;": { "p": [10888], "c": "\u2A88" }, + "gneqq;": { "p": [8809], "c": "\u2269" }, + "gnsim;": { "p": [8935], "c": "\u22E7" }, + "gopf;": { "p": [120152], "c": "\uD835\uDD58" }, + "grave;": { "p": [96], "c": "\u0060" }, + "gscr;": { "p": [8458], "c": "\u210A" }, + "gsim;": { "p": [8819], "c": "\u2273" }, + "gsime;": { "p": [10894], "c": "\u2A8E" }, + "gsiml;": { "p": [10896], "c": "\u2A90" }, + "gt": { "p": [62], "c": "\u003E" }, + "gt;": { "p": [62], "c": "\u003E" }, + "gtcc;": { "p": [10919], "c": "\u2AA7" }, + "gtcir;": { "p": [10874], "c": "\u2A7A" }, + "gtdot;": { "p": [8919], "c": "\u22D7" }, + "gtlPar;": { "p": [10645], "c": "\u2995" }, + "gtquest;": { "p": [10876], "c": "\u2A7C" }, + "gtrapprox;": { "p": [10886], "c": "\u2A86" }, + "gtrarr;": { "p": [10616], "c": "\u2978" }, + "gtrdot;": { "p": [8919], "c": "\u22D7" }, + "gtreqless;": { "p": [8923], "c": "\u22DB" }, + "gtreqqless;": { "p": [10892], "c": "\u2A8C" }, + "gtrless;": { "p": [8823], "c": "\u2277" }, + "gtrsim;": { "p": [8819], "c": "\u2273" }, + "gvertneqq;": { + "p": [8809, 65024], + "c": "\u2269\uFE00" + }, + "gvnE;": { "p": [8809, 65024], "c": "\u2269\uFE00" }, + "hArr;": { "p": [8660], "c": "\u21D4" }, + "hairsp;": { "p": [8202], "c": "\u200A" }, + "half;": { "p": [189], "c": "\u00BD" }, + "hamilt;": { "p": [8459], "c": "\u210B" }, + "hardcy;": { "p": [1098], "c": "\u044A" }, + "harr;": { "p": [8596], "c": "\u2194" }, + "harrcir;": { "p": [10568], "c": "\u2948" }, + "harrw;": { "p": [8621], "c": "\u21AD" }, + "hbar;": { "p": [8463], "c": "\u210F" }, + "hcirc;": { "p": [293], "c": "\u0125" }, + "hearts;": { "p": [9829], "c": "\u2665" }, + "heartsuit;": { "p": [9829], "c": "\u2665" }, + "hellip;": { "p": [8230], "c": "\u2026" }, + "hercon;": { "p": [8889], "c": "\u22B9" }, + "hfr;": { "p": [120101], "c": "\uD835\uDD25" }, + "hksearow;": { "p": [10533], "c": "\u2925" }, + "hkswarow;": { "p": [10534], "c": "\u2926" }, + "hoarr;": { "p": [8703], "c": "\u21FF" }, + "homtht;": { "p": [8763], "c": "\u223B" }, + "hookleftarrow;": { "p": [8617], "c": "\u21A9" }, + "hookrightarrow;": { "p": [8618], "c": "\u21AA" }, + "hopf;": { "p": [120153], "c": "\uD835\uDD59" }, + "horbar;": { "p": [8213], "c": "\u2015" }, + "hscr;": { "p": [119997], "c": "\uD835\uDCBD" }, + "hslash;": { "p": [8463], "c": "\u210F" }, + "hstrok;": { "p": [295], "c": "\u0127" }, + "hybull;": { "p": [8259], "c": "\u2043" }, + "hyphen;": { "p": [8208], "c": "\u2010" }, + "iacute": { "p": [237], "c": "\u00ED" }, + "iacute;": { "p": [237], "c": "\u00ED" }, + "ic;": { "p": [8291], "c": "\u2063" }, + "icirc": { "p": [238], "c": "\u00EE" }, + "icirc;": { "p": [238], "c": "\u00EE" }, + "icy;": { "p": [1080], "c": "\u0438" }, + "iecy;": { "p": [1077], "c": "\u0435" }, + "iexcl": { "p": [161], "c": "\u00A1" }, + "iexcl;": { "p": [161], "c": "\u00A1" }, + "iff;": { "p": [8660], "c": "\u21D4" }, + "ifr;": { "p": [120102], "c": "\uD835\uDD26" }, + "igrave": { "p": [236], "c": "\u00EC" }, + "igrave;": { "p": [236], "c": "\u00EC" }, + "ii;": { "p": [8520], "c": "\u2148" }, + "iiiint;": { "p": [10764], "c": "\u2A0C" }, + "iiint;": { "p": [8749], "c": "\u222D" }, + "iinfin;": { "p": [10716], "c": "\u29DC" }, + "iiota;": { "p": [8489], "c": "\u2129" }, + "ijlig;": { "p": [307], "c": "\u0133" }, + "imacr;": { "p": [299], "c": "\u012B" }, + "image;": { "p": [8465], "c": "\u2111" }, + "imagline;": { "p": [8464], "c": "\u2110" }, + "imagpart;": { "p": [8465], "c": "\u2111" }, + "imath;": { "p": [305], "c": "\u0131" }, + "imof;": { "p": [8887], "c": "\u22B7" }, + "imped;": { "p": [437], "c": "\u01B5" }, + "in;": { "p": [8712], "c": "\u2208" }, + "incare;": { "p": [8453], "c": "\u2105" }, + "infin;": { "p": [8734], "c": "\u221E" }, + "infintie;": { "p": [10717], "c": "\u29DD" }, + "inodot;": { "p": [305], "c": "\u0131" }, + "int;": { "p": [8747], "c": "\u222B" }, + "intcal;": { "p": [8890], "c": "\u22BA" }, + "integers;": { "p": [8484], "c": "\u2124" }, + "intercal;": { "p": [8890], "c": "\u22BA" }, + "intlarhk;": { "p": [10775], "c": "\u2A17" }, + "intprod;": { "p": [10812], "c": "\u2A3C" }, + "iocy;": { "p": [1105], "c": "\u0451" }, + "iogon;": { "p": [303], "c": "\u012F" }, + "iopf;": { "p": [120154], "c": "\uD835\uDD5A" }, + "iota;": { "p": [953], "c": "\u03B9" }, + "iprod;": { "p": [10812], "c": "\u2A3C" }, + "iquest": { "p": [191], "c": "\u00BF" }, + "iquest;": { "p": [191], "c": "\u00BF" }, + "iscr;": { "p": [119998], "c": "\uD835\uDCBE" }, + "isin;": { "p": [8712], "c": "\u2208" }, + "isinE;": { "p": [8953], "c": "\u22F9" }, + "isindot;": { "p": [8949], "c": "\u22F5" }, + "isins;": { "p": [8948], "c": "\u22F4" }, + "isinsv;": { "p": [8947], "c": "\u22F3" }, + "isinv;": { "p": [8712], "c": "\u2208" }, + "it;": { "p": [8290], "c": "\u2062" }, + "itilde;": { "p": [297], "c": "\u0129" }, + "iukcy;": { "p": [1110], "c": "\u0456" }, + "iuml": { "p": [239], "c": "\u00EF" }, + "iuml;": { "p": [239], "c": "\u00EF" }, + "jcirc;": { "p": [309], "c": "\u0135" }, + "jcy;": { "p": [1081], "c": "\u0439" }, + "jfr;": { "p": [120103], "c": "\uD835\uDD27" }, + "jmath;": { "p": [567], "c": "\u0237" }, + "jopf;": { "p": [120155], "c": "\uD835\uDD5B" }, + "jscr;": { "p": [119999], "c": "\uD835\uDCBF" }, + "jsercy;": { "p": [1112], "c": "\u0458" }, + "jukcy;": { "p": [1108], "c": "\u0454" }, + "kappa;": { "p": [954], "c": "\u03BA" }, + "kappav;": { "p": [1008], "c": "\u03F0" }, + "kcedil;": { "p": [311], "c": "\u0137" }, + "kcy;": { "p": [1082], "c": "\u043A" }, + "kfr;": { "p": [120104], "c": "\uD835\uDD28" }, + "kgreen;": { "p": [312], "c": "\u0138" }, + "khcy;": { "p": [1093], "c": "\u0445" }, + "kjcy;": { "p": [1116], "c": "\u045C" }, + "kopf;": { "p": [120156], "c": "\uD835\uDD5C" }, + "kscr;": { "p": [120000], "c": "\uD835\uDCC0" }, + "lAarr;": { "p": [8666], "c": "\u21DA" }, + "lArr;": { "p": [8656], "c": "\u21D0" }, + "lAtail;": { "p": [10523], "c": "\u291B" }, + "lBarr;": { "p": [10510], "c": "\u290E" }, + "lE;": { "p": [8806], "c": "\u2266" }, + "lEg;": { "p": [10891], "c": "\u2A8B" }, + "lHar;": { "p": [10594], "c": "\u2962" }, + "lacute;": { "p": [314], "c": "\u013A" }, + "laemptyv;": { "p": [10676], "c": "\u29B4" }, + "lagran;": { "p": [8466], "c": "\u2112" }, + "lambda;": { "p": [955], "c": "\u03BB" }, + "lang;": { "p": [10216], "c": "\u27E8" }, + "langd;": { "p": [10641], "c": "\u2991" }, + "langle;": { "p": [10216], "c": "\u27E8" }, + "lap;": { "p": [10885], "c": "\u2A85" }, + "laquo": { "p": [171], "c": "\u00AB" }, + "laquo;": { "p": [171], "c": "\u00AB" }, + "larr;": { "p": [8592], "c": "\u2190" }, + "larrb;": { "p": [8676], "c": "\u21E4" }, + "larrbfs;": { "p": [10527], "c": "\u291F" }, + "larrfs;": { "p": [10525], "c": "\u291D" }, + "larrhk;": { "p": [8617], "c": "\u21A9" }, + "larrlp;": { "p": [8619], "c": "\u21AB" }, + "larrpl;": { "p": [10553], "c": "\u2939" }, + "larrsim;": { "p": [10611], "c": "\u2973" }, + "larrtl;": { "p": [8610], "c": "\u21A2" }, + "lat;": { "p": [10923], "c": "\u2AAB" }, + "latail;": { "p": [10521], "c": "\u2919" }, + "late;": { "p": [10925], "c": "\u2AAD" }, + "lates;": { "p": [10925, 65024], "c": "\u2AAD\uFE00" }, + "lbarr;": { "p": [10508], "c": "\u290C" }, + "lbbrk;": { "p": [10098], "c": "\u2772" }, + "lbrace;": { "p": [123], "c": "\u007B" }, + "lbrack;": { "p": [91], "c": "\u005B" }, + "lbrke;": { "p": [10635], "c": "\u298B" }, + "lbrksld;": { "p": [10639], "c": "\u298F" }, + "lbrkslu;": { "p": [10637], "c": "\u298D" }, + "lcaron;": { "p": [318], "c": "\u013E" }, + "lcedil;": { "p": [316], "c": "\u013C" }, + "lceil;": { "p": [8968], "c": "\u2308" }, + "lcub;": { "p": [123], "c": "\u007B" }, + "lcy;": { "p": [1083], "c": "\u043B" }, + "ldca;": { "p": [10550], "c": "\u2936" }, + "ldquo;": { "p": [8220], "c": "\u201C" }, + "ldquor;": { "p": [8222], "c": "\u201E" }, + "ldrdhar;": { "p": [10599], "c": "\u2967" }, + "ldrushar;": { "p": [10571], "c": "\u294B" }, + "ldsh;": { "p": [8626], "c": "\u21B2" }, + "le;": { "p": [8804], "c": "\u2264" }, + "leftarrow;": { "p": [8592], "c": "\u2190" }, + "leftarrowtail;": { "p": [8610], "c": "\u21A2" }, + "leftharpoondown;": { "p": [8637], "c": "\u21BD" }, + "leftharpoonup;": { "p": [8636], "c": "\u21BC" }, + "leftleftarrows;": { "p": [8647], "c": "\u21C7" }, + "leftrightarrow;": { "p": [8596], "c": "\u2194" }, + "leftrightarrows;": { "p": [8646], "c": "\u21C6" }, + "leftrightharpoons;": { "p": [8651], "c": "\u21CB" }, + "leftrightsquigarrow;": { "p": [8621], "c": "\u21AD" }, + "leftthreetimes;": { "p": [8907], "c": "\u22CB" }, + "leg;": { "p": [8922], "c": "\u22DA" }, + "leq;": { "p": [8804], "c": "\u2264" }, + "leqq;": { "p": [8806], "c": "\u2266" }, + "leqslant;": { "p": [10877], "c": "\u2A7D" }, + "les;": { "p": [10877], "c": "\u2A7D" }, + "lescc;": { "p": [10920], "c": "\u2AA8" }, + "lesdot;": { "p": [10879], "c": "\u2A7F" }, + "lesdoto;": { "p": [10881], "c": "\u2A81" }, + "lesdotor;": { "p": [10883], "c": "\u2A83" }, + "lesg;": { "p": [8922, 65024], "c": "\u22DA\uFE00" }, + "lesges;": { "p": [10899], "c": "\u2A93" }, + "lessapprox;": { "p": [10885], "c": "\u2A85" }, + "lessdot;": { "p": [8918], "c": "\u22D6" }, + "lesseqgtr;": { "p": [8922], "c": "\u22DA" }, + "lesseqqgtr;": { "p": [10891], "c": "\u2A8B" }, + "lessgtr;": { "p": [8822], "c": "\u2276" }, + "lesssim;": { "p": [8818], "c": "\u2272" }, + "lfisht;": { "p": [10620], "c": "\u297C" }, + "lfloor;": { "p": [8970], "c": "\u230A" }, + "lfr;": { "p": [120105], "c": "\uD835\uDD29" }, + "lg;": { "p": [8822], "c": "\u2276" }, + "lgE;": { "p": [10897], "c": "\u2A91" }, + "lhard;": { "p": [8637], "c": "\u21BD" }, + "lharu;": { "p": [8636], "c": "\u21BC" }, + "lharul;": { "p": [10602], "c": "\u296A" }, + "lhblk;": { "p": [9604], "c": "\u2584" }, + "ljcy;": { "p": [1113], "c": "\u0459" }, + "ll;": { "p": [8810], "c": "\u226A" }, + "llarr;": { "p": [8647], "c": "\u21C7" }, + "llcorner;": { "p": [8990], "c": "\u231E" }, + "llhard;": { "p": [10603], "c": "\u296B" }, + "lltri;": { "p": [9722], "c": "\u25FA" }, + "lmidot;": { "p": [320], "c": "\u0140" }, + "lmoust;": { "p": [9136], "c": "\u23B0" }, + "lmoustache;": { "p": [9136], "c": "\u23B0" }, + "lnE;": { "p": [8808], "c": "\u2268" }, + "lnap;": { "p": [10889], "c": "\u2A89" }, + "lnapprox;": { "p": [10889], "c": "\u2A89" }, + "lne;": { "p": [10887], "c": "\u2A87" }, + "lneq;": { "p": [10887], "c": "\u2A87" }, + "lneqq;": { "p": [8808], "c": "\u2268" }, + "lnsim;": { "p": [8934], "c": "\u22E6" }, + "loang;": { "p": [10220], "c": "\u27EC" }, + "loarr;": { "p": [8701], "c": "\u21FD" }, + "lobrk;": { "p": [10214], "c": "\u27E6" }, + "longleftarrow;": { "p": [10229], "c": "\u27F5" }, + "longleftrightarrow;": { "p": [10231], "c": "\u27F7" }, + "longmapsto;": { "p": [10236], "c": "\u27FC" }, + "longrightarrow;": { "p": [10230], "c": "\u27F6" }, + "looparrowleft;": { "p": [8619], "c": "\u21AB" }, + "looparrowright;": { "p": [8620], "c": "\u21AC" }, + "lopar;": { "p": [10629], "c": "\u2985" }, + "lopf;": { "p": [120157], "c": "\uD835\uDD5D" }, + "loplus;": { "p": [10797], "c": "\u2A2D" }, + "lotimes;": { "p": [10804], "c": "\u2A34" }, + "lowast;": { "p": [8727], "c": "\u2217" }, + "lowbar;": { "p": [95], "c": "\u005F" }, + "loz;": { "p": [9674], "c": "\u25CA" }, + "lozenge;": { "p": [9674], "c": "\u25CA" }, + "lozf;": { "p": [10731], "c": "\u29EB" }, + "lpar;": { "p": [40], "c": "\u0028" }, + "lparlt;": { "p": [10643], "c": "\u2993" }, + "lrarr;": { "p": [8646], "c": "\u21C6" }, + "lrcorner;": { "p": [8991], "c": "\u231F" }, + "lrhar;": { "p": [8651], "c": "\u21CB" }, + "lrhard;": { "p": [10605], "c": "\u296D" }, + "lrm;": { "p": [8206], "c": "\u200E" }, + "lrtri;": { "p": [8895], "c": "\u22BF" }, + "lsaquo;": { "p": [8249], "c": "\u2039" }, + "lscr;": { "p": [120001], "c": "\uD835\uDCC1" }, + "lsh;": { "p": [8624], "c": "\u21B0" }, + "lsim;": { "p": [8818], "c": "\u2272" }, + "lsime;": { "p": [10893], "c": "\u2A8D" }, + "lsimg;": { "p": [10895], "c": "\u2A8F" }, + "lsqb;": { "p": [91], "c": "\u005B" }, + "lsquo;": { "p": [8216], "c": "\u2018" }, + "lsquor;": { "p": [8218], "c": "\u201A" }, + "lstrok;": { "p": [322], "c": "\u0142" }, + "lt": { "p": [60], "c": "\u003C" }, + "lt;": { "p": [60], "c": "\u003C" }, + "ltcc;": { "p": [10918], "c": "\u2AA6" }, + "ltcir;": { "p": [10873], "c": "\u2A79" }, + "ltdot;": { "p": [8918], "c": "\u22D6" }, + "lthree;": { "p": [8907], "c": "\u22CB" }, + "ltimes;": { "p": [8905], "c": "\u22C9" }, + "ltlarr;": { "p": [10614], "c": "\u2976" }, + "ltquest;": { "p": [10875], "c": "\u2A7B" }, + "ltrPar;": { "p": [10646], "c": "\u2996" }, + "ltri;": { "p": [9667], "c": "\u25C3" }, + "ltrie;": { "p": [8884], "c": "\u22B4" }, + "ltrif;": { "p": [9666], "c": "\u25C2" }, + "lurdshar;": { "p": [10570], "c": "\u294A" }, + "luruhar;": { "p": [10598], "c": "\u2966" }, + "lvertneqq;": { + "p": [8808, 65024], + "c": "\u2268\uFE00" + }, + "lvnE;": { "p": [8808, 65024], "c": "\u2268\uFE00" }, + "mDDot;": { "p": [8762], "c": "\u223A" }, + "macr": { "p": [175], "c": "\u00AF" }, + "macr;": { "p": [175], "c": "\u00AF" }, + "male;": { "p": [9794], "c": "\u2642" }, + "malt;": { "p": [10016], "c": "\u2720" }, + "maltese;": { "p": [10016], "c": "\u2720" }, + "map;": { "p": [8614], "c": "\u21A6" }, + "mapsto;": { "p": [8614], "c": "\u21A6" }, + "mapstodown;": { "p": [8615], "c": "\u21A7" }, + "mapstoleft;": { "p": [8612], "c": "\u21A4" }, + "mapstoup;": { "p": [8613], "c": "\u21A5" }, + "marker;": { "p": [9646], "c": "\u25AE" }, + "mcomma;": { "p": [10793], "c": "\u2A29" }, + "mcy;": { "p": [1084], "c": "\u043C" }, + "mdash;": { "p": [8212], "c": "\u2014" }, + "measuredangle;": { "p": [8737], "c": "\u2221" }, + "mfr;": { "p": [120106], "c": "\uD835\uDD2A" }, + "mho;": { "p": [8487], "c": "\u2127" }, + "micro": { "p": [181], "c": "\u00B5" }, + "micro;": { "p": [181], "c": "\u00B5" }, + "mid;": { "p": [8739], "c": "\u2223" }, + "midast;": { "p": [42], "c": "\u002A" }, + "midcir;": { "p": [10992], "c": "\u2AF0" }, + "middot": { "p": [183], "c": "\u00B7" }, + "middot;": { "p": [183], "c": "\u00B7" }, + "minus;": { "p": [8722], "c": "\u2212" }, + "minusb;": { "p": [8863], "c": "\u229F" }, + "minusd;": { "p": [8760], "c": "\u2238" }, + "minusdu;": { "p": [10794], "c": "\u2A2A" }, + "mlcp;": { "p": [10971], "c": "\u2ADB" }, + "mldr;": { "p": [8230], "c": "\u2026" }, + "mnplus;": { "p": [8723], "c": "\u2213" }, + "models;": { "p": [8871], "c": "\u22A7" }, + "mopf;": { "p": [120158], "c": "\uD835\uDD5E" }, + "mp;": { "p": [8723], "c": "\u2213" }, + "mscr;": { "p": [120002], "c": "\uD835\uDCC2" }, + "mstpos;": { "p": [8766], "c": "\u223E" }, + "mu;": { "p": [956], "c": "\u03BC" }, + "multimap;": { "p": [8888], "c": "\u22B8" }, + "mumap;": { "p": [8888], "c": "\u22B8" }, + "nGg;": { "p": [8921, 824], "c": "\u22D9\u0338" }, + "nGt;": { "p": [8811, 8402], "c": "\u226B\u20D2" }, + "nGtv;": { "p": [8811, 824], "c": "\u226B\u0338" }, + "nLeftarrow;": { "p": [8653], "c": "\u21CD" }, + "nLeftrightarrow;": { "p": [8654], "c": "\u21CE" }, + "nLl;": { "p": [8920, 824], "c": "\u22D8\u0338" }, + "nLt;": { "p": [8810, 8402], "c": "\u226A\u20D2" }, + "nLtv;": { "p": [8810, 824], "c": "\u226A\u0338" }, + "nRightarrow;": { "p": [8655], "c": "\u21CF" }, + "nVDash;": { "p": [8879], "c": "\u22AF" }, + "nVdash;": { "p": [8878], "c": "\u22AE" }, + "nabla;": { "p": [8711], "c": "\u2207" }, + "nacute;": { "p": [324], "c": "\u0144" }, + "nang;": { "p": [8736, 8402], "c": "\u2220\u20D2" }, + "nap;": { "p": [8777], "c": "\u2249" }, + "napE;": { "p": [10864, 824], "c": "\u2A70\u0338" }, + "napid;": { "p": [8779, 824], "c": "\u224B\u0338" }, + "napos;": { "p": [329], "c": "\u0149" }, + "napprox;": { "p": [8777], "c": "\u2249" }, + "natur;": { "p": [9838], "c": "\u266E" }, + "natural;": { "p": [9838], "c": "\u266E" }, + "naturals;": { "p": [8469], "c": "\u2115" }, + "nbsp": { "p": [160], "c": "\u00A0" }, + "nbsp;": { "p": [160], "c": "\u00A0" }, + "nbump;": { "p": [8782, 824], "c": "\u224E\u0338" }, + "nbumpe;": { "p": [8783, 824], "c": "\u224F\u0338" }, + "ncap;": { "p": [10819], "c": "\u2A43" }, + "ncaron;": { "p": [328], "c": "\u0148" }, + "ncedil;": { "p": [326], "c": "\u0146" }, + "ncong;": { "p": [8775], "c": "\u2247" }, + "ncongdot;": { "p": [10861, 824], "c": "\u2A6D\u0338" }, + "ncup;": { "p": [10818], "c": "\u2A42" }, + "ncy;": { "p": [1085], "c": "\u043D" }, + "ndash;": { "p": [8211], "c": "\u2013" }, + "ne;": { "p": [8800], "c": "\u2260" }, + "neArr;": { "p": [8663], "c": "\u21D7" }, + "nearhk;": { "p": [10532], "c": "\u2924" }, + "nearr;": { "p": [8599], "c": "\u2197" }, + "nearrow;": { "p": [8599], "c": "\u2197" }, + "nedot;": { "p": [8784, 824], "c": "\u2250\u0338" }, + "nequiv;": { "p": [8802], "c": "\u2262" }, + "nesear;": { "p": [10536], "c": "\u2928" }, + "nesim;": { "p": [8770, 824], "c": "\u2242\u0338" }, + "nexist;": { "p": [8708], "c": "\u2204" }, + "nexists;": { "p": [8708], "c": "\u2204" }, + "nfr;": { "p": [120107], "c": "\uD835\uDD2B" }, + "ngE;": { "p": [8807, 824], "c": "\u2267\u0338" }, + "nge;": { "p": [8817], "c": "\u2271" }, + "ngeq;": { "p": [8817], "c": "\u2271" }, + "ngeqq;": { "p": [8807, 824], "c": "\u2267\u0338" }, + "ngeqslant;": { "p": [10878, 824], "c": "\u2A7E\u0338" }, + "nges;": { "p": [10878, 824], "c": "\u2A7E\u0338" }, + "ngsim;": { "p": [8821], "c": "\u2275" }, + "ngt;": { "p": [8815], "c": "\u226F" }, + "ngtr;": { "p": [8815], "c": "\u226F" }, + "nhArr;": { "p": [8654], "c": "\u21CE" }, + "nharr;": { "p": [8622], "c": "\u21AE" }, + "nhpar;": { "p": [10994], "c": "\u2AF2" }, + "ni;": { "p": [8715], "c": "\u220B" }, + "nis;": { "p": [8956], "c": "\u22FC" }, + "nisd;": { "p": [8954], "c": "\u22FA" }, + "niv;": { "p": [8715], "c": "\u220B" }, + "njcy;": { "p": [1114], "c": "\u045A" }, + "nlArr;": { "p": [8653], "c": "\u21CD" }, + "nlE;": { "p": [8806, 824], "c": "\u2266\u0338" }, + "nlarr;": { "p": [8602], "c": "\u219A" }, + "nldr;": { "p": [8229], "c": "\u2025" }, + "nle;": { "p": [8816], "c": "\u2270" }, + "nleftarrow;": { "p": [8602], "c": "\u219A" }, + "nleftrightarrow;": { "p": [8622], "c": "\u21AE" }, + "nleq;": { "p": [8816], "c": "\u2270" }, + "nleqq;": { "p": [8806, 824], "c": "\u2266\u0338" }, + "nleqslant;": { "p": [10877, 824], "c": "\u2A7D\u0338" }, + "nles;": { "p": [10877, 824], "c": "\u2A7D\u0338" }, + "nless;": { "p": [8814], "c": "\u226E" }, + "nlsim;": { "p": [8820], "c": "\u2274" }, + "nlt;": { "p": [8814], "c": "\u226E" }, + "nltri;": { "p": [8938], "c": "\u22EA" }, + "nltrie;": { "p": [8940], "c": "\u22EC" }, + "nmid;": { "p": [8740], "c": "\u2224" }, + "nopf;": { "p": [120159], "c": "\uD835\uDD5F" }, + "not": { "p": [172], "c": "\u00AC" }, + "not;": { "p": [172], "c": "\u00AC" }, + "notin;": { "p": [8713], "c": "\u2209" }, + "notinE;": { "p": [8953, 824], "c": "\u22F9\u0338" }, + "notindot;": { "p": [8949, 824], "c": "\u22F5\u0338" }, + "notinva;": { "p": [8713], "c": "\u2209" }, + "notinvb;": { "p": [8951], "c": "\u22F7" }, + "notinvc;": { "p": [8950], "c": "\u22F6" }, + "notni;": { "p": [8716], "c": "\u220C" }, + "notniva;": { "p": [8716], "c": "\u220C" }, + "notnivb;": { "p": [8958], "c": "\u22FE" }, + "notnivc;": { "p": [8957], "c": "\u22FD" }, + "npar;": { "p": [8742], "c": "\u2226" }, + "nparallel;": { "p": [8742], "c": "\u2226" }, + "nparsl;": { "p": [11005, 8421], "c": "\u2AFD\u20E5" }, + "npart;": { "p": [8706, 824], "c": "\u2202\u0338" }, + "npolint;": { "p": [10772], "c": "\u2A14" }, + "npr;": { "p": [8832], "c": "\u2280" }, + "nprcue;": { "p": [8928], "c": "\u22E0" }, + "npre;": { "p": [10927, 824], "c": "\u2AAF\u0338" }, + "nprec;": { "p": [8832], "c": "\u2280" }, + "npreceq;": { "p": [10927, 824], "c": "\u2AAF\u0338" }, + "nrArr;": { "p": [8655], "c": "\u21CF" }, + "nrarr;": { "p": [8603], "c": "\u219B" }, + "nrarrc;": { "p": [10547, 824], "c": "\u2933\u0338" }, + "nrarrw;": { "p": [8605, 824], "c": "\u219D\u0338" }, + "nrightarrow;": { "p": [8603], "c": "\u219B" }, + "nrtri;": { "p": [8939], "c": "\u22EB" }, + "nrtrie;": { "p": [8941], "c": "\u22ED" }, + "nsc;": { "p": [8833], "c": "\u2281" }, + "nsccue;": { "p": [8929], "c": "\u22E1" }, + "nsce;": { "p": [10928, 824], "c": "\u2AB0\u0338" }, + "nscr;": { "p": [120003], "c": "\uD835\uDCC3" }, + "nshortmid;": { "p": [8740], "c": "\u2224" }, + "nshortparallel;": { "p": [8742], "c": "\u2226" }, + "nsim;": { "p": [8769], "c": "\u2241" }, + "nsime;": { "p": [8772], "c": "\u2244" }, + "nsimeq;": { "p": [8772], "c": "\u2244" }, + "nsmid;": { "p": [8740], "c": "\u2224" }, + "nspar;": { "p": [8742], "c": "\u2226" }, + "nsqsube;": { "p": [8930], "c": "\u22E2" }, + "nsqsupe;": { "p": [8931], "c": "\u22E3" }, + "nsub;": { "p": [8836], "c": "\u2284" }, + "nsubE;": { "p": [10949, 824], "c": "\u2AC5\u0338" }, + "nsube;": { "p": [8840], "c": "\u2288" }, + "nsubset;": { "p": [8834, 8402], "c": "\u2282\u20D2" }, + "nsubseteq;": { "p": [8840], "c": "\u2288" }, + "nsubseteqq;": { + "p": [10949, 824], + "c": "\u2AC5\u0338" + }, + "nsucc;": { "p": [8833], "c": "\u2281" }, + "nsucceq;": { "p": [10928, 824], "c": "\u2AB0\u0338" }, + "nsup;": { "p": [8837], "c": "\u2285" }, + "nsupE;": { "p": [10950, 824], "c": "\u2AC6\u0338" }, + "nsupe;": { "p": [8841], "c": "\u2289" }, + "nsupset;": { "p": [8835, 8402], "c": "\u2283\u20D2" }, + "nsupseteq;": { "p": [8841], "c": "\u2289" }, + "nsupseteqq;": { + "p": [10950, 824], + "c": "\u2AC6\u0338" + }, + "ntgl;": { "p": [8825], "c": "\u2279" }, + "ntilde": { "p": [241], "c": "\u00F1" }, + "ntilde;": { "p": [241], "c": "\u00F1" }, + "ntlg;": { "p": [8824], "c": "\u2278" }, + "ntriangleleft;": { "p": [8938], "c": "\u22EA" }, + "ntrianglelefteq;": { "p": [8940], "c": "\u22EC" }, + "ntriangleright;": { "p": [8939], "c": "\u22EB" }, + "ntrianglerighteq;": { "p": [8941], "c": "\u22ED" }, + "nu;": { "p": [957], "c": "\u03BD" }, + "num;": { "p": [35], "c": "\u0023" }, + "numero;": { "p": [8470], "c": "\u2116" }, + "numsp;": { "p": [8199], "c": "\u2007" }, + "nvDash;": { "p": [8877], "c": "\u22AD" }, + "nvHarr;": { "p": [10500], "c": "\u2904" }, + "nvap;": { "p": [8781, 8402], "c": "\u224D\u20D2" }, + "nvdash;": { "p": [8876], "c": "\u22AC" }, + "nvge;": { "p": [8805, 8402], "c": "\u2265\u20D2" }, + "nvgt;": { "p": [62, 8402], "c": "\u003E\u20D2" }, + "nvinfin;": { "p": [10718], "c": "\u29DE" }, + "nvlArr;": { "p": [10498], "c": "\u2902" }, + "nvle;": { "p": [8804, 8402], "c": "\u2264\u20D2" }, + "nvlt;": { "p": [60, 8402], "c": "\u003C\u20D2" }, + "nvltrie;": { "p": [8884, 8402], "c": "\u22B4\u20D2" }, + "nvrArr;": { "p": [10499], "c": "\u2903" }, + "nvrtrie;": { "p": [8885, 8402], "c": "\u22B5\u20D2" }, + "nvsim;": { "p": [8764, 8402], "c": "\u223C\u20D2" }, + "nwArr;": { "p": [8662], "c": "\u21D6" }, + "nwarhk;": { "p": [10531], "c": "\u2923" }, + "nwarr;": { "p": [8598], "c": "\u2196" }, + "nwarrow;": { "p": [8598], "c": "\u2196" }, + "nwnear;": { "p": [10535], "c": "\u2927" }, + "oS;": { "p": [9416], "c": "\u24C8" }, + "oacute": { "p": [243], "c": "\u00F3" }, + "oacute;": { "p": [243], "c": "\u00F3" }, + "oast;": { "p": [8859], "c": "\u229B" }, + "ocir;": { "p": [8858], "c": "\u229A" }, + "ocirc": { "p": [244], "c": "\u00F4" }, + "ocirc;": { "p": [244], "c": "\u00F4" }, + "ocy;": { "p": [1086], "c": "\u043E" }, + "odash;": { "p": [8861], "c": "\u229D" }, + "odblac;": { "p": [337], "c": "\u0151" }, + "odiv;": { "p": [10808], "c": "\u2A38" }, + "odot;": { "p": [8857], "c": "\u2299" }, + "odsold;": { "p": [10684], "c": "\u29BC" }, + "oelig;": { "p": [339], "c": "\u0153" }, + "ofcir;": { "p": [10687], "c": "\u29BF" }, + "ofr;": { "p": [120108], "c": "\uD835\uDD2C" }, + "ogon;": { "p": [731], "c": "\u02DB" }, + "ograve": { "p": [242], "c": "\u00F2" }, + "ograve;": { "p": [242], "c": "\u00F2" }, + "ogt;": { "p": [10689], "c": "\u29C1" }, + "ohbar;": { "p": [10677], "c": "\u29B5" }, + "ohm;": { "p": [937], "c": "\u03A9" }, + "oint;": { "p": [8750], "c": "\u222E" }, + "olarr;": { "p": [8634], "c": "\u21BA" }, + "olcir;": { "p": [10686], "c": "\u29BE" }, + "olcross;": { "p": [10683], "c": "\u29BB" }, + "oline;": { "p": [8254], "c": "\u203E" }, + "olt;": { "p": [10688], "c": "\u29C0" }, + "omacr;": { "p": [333], "c": "\u014D" }, + "omega;": { "p": [969], "c": "\u03C9" }, + "omicron;": { "p": [959], "c": "\u03BF" }, + "omid;": { "p": [10678], "c": "\u29B6" }, + "ominus;": { "p": [8854], "c": "\u2296" }, + "oopf;": { "p": [120160], "c": "\uD835\uDD60" }, + "opar;": { "p": [10679], "c": "\u29B7" }, + "operp;": { "p": [10681], "c": "\u29B9" }, + "oplus;": { "p": [8853], "c": "\u2295" }, + "or;": { "p": [8744], "c": "\u2228" }, + "orarr;": { "p": [8635], "c": "\u21BB" }, + "ord;": { "p": [10845], "c": "\u2A5D" }, + "order;": { "p": [8500], "c": "\u2134" }, + "orderof;": { "p": [8500], "c": "\u2134" }, + "ordf": { "p": [170], "c": "\u00AA" }, + "ordf;": { "p": [170], "c": "\u00AA" }, + "ordm": { "p": [186], "c": "\u00BA" }, + "ordm;": { "p": [186], "c": "\u00BA" }, + "origof;": { "p": [8886], "c": "\u22B6" }, + "oror;": { "p": [10838], "c": "\u2A56" }, + "orslope;": { "p": [10839], "c": "\u2A57" }, + "orv;": { "p": [10843], "c": "\u2A5B" }, + "oscr;": { "p": [8500], "c": "\u2134" }, + "oslash": { "p": [248], "c": "\u00F8" }, + "oslash;": { "p": [248], "c": "\u00F8" }, + "osol;": { "p": [8856], "c": "\u2298" }, + "otilde": { "p": [245], "c": "\u00F5" }, + "otilde;": { "p": [245], "c": "\u00F5" }, + "otimes;": { "p": [8855], "c": "\u2297" }, + "otimesas;": { "p": [10806], "c": "\u2A36" }, + "ouml": { "p": [246], "c": "\u00F6" }, + "ouml;": { "p": [246], "c": "\u00F6" }, + "ovbar;": { "p": [9021], "c": "\u233D" }, + "par;": { "p": [8741], "c": "\u2225" }, + "para": { "p": [182], "c": "\u00B6" }, + "para;": { "p": [182], "c": "\u00B6" }, + "parallel;": { "p": [8741], "c": "\u2225" }, + "parsim;": { "p": [10995], "c": "\u2AF3" }, + "parsl;": { "p": [11005], "c": "\u2AFD" }, + "part;": { "p": [8706], "c": "\u2202" }, + "pcy;": { "p": [1087], "c": "\u043F" }, + "percnt;": { "p": [37], "c": "\u0025" }, + "period;": { "p": [46], "c": "\u002E" }, + "permil;": { "p": [8240], "c": "\u2030" }, + "perp;": { "p": [8869], "c": "\u22A5" }, + "pertenk;": { "p": [8241], "c": "\u2031" }, + "pfr;": { "p": [120109], "c": "\uD835\uDD2D" }, + "phi;": { "p": [966], "c": "\u03C6" }, + "phiv;": { "p": [981], "c": "\u03D5" }, + "phmmat;": { "p": [8499], "c": "\u2133" }, + "phone;": { "p": [9742], "c": "\u260E" }, + "pi;": { "p": [960], "c": "\u03C0" }, + "pitchfork;": { "p": [8916], "c": "\u22D4" }, + "piv;": { "p": [982], "c": "\u03D6" }, + "planck;": { "p": [8463], "c": "\u210F" }, + "planckh;": { "p": [8462], "c": "\u210E" }, + "plankv;": { "p": [8463], "c": "\u210F" }, + "plus;": { "p": [43], "c": "\u002B" }, + "plusacir;": { "p": [10787], "c": "\u2A23" }, + "plusb;": { "p": [8862], "c": "\u229E" }, + "pluscir;": { "p": [10786], "c": "\u2A22" }, + "plusdo;": { "p": [8724], "c": "\u2214" }, + "plusdu;": { "p": [10789], "c": "\u2A25" }, + "pluse;": { "p": [10866], "c": "\u2A72" }, + "plusmn": { "p": [177], "c": "\u00B1" }, + "plusmn;": { "p": [177], "c": "\u00B1" }, + "plussim;": { "p": [10790], "c": "\u2A26" }, + "plustwo;": { "p": [10791], "c": "\u2A27" }, + "pm;": { "p": [177], "c": "\u00B1" }, + "pointint;": { "p": [10773], "c": "\u2A15" }, + "popf;": { "p": [120161], "c": "\uD835\uDD61" }, + "pound": { "p": [163], "c": "\u00A3" }, + "pound;": { "p": [163], "c": "\u00A3" }, + "pr;": { "p": [8826], "c": "\u227A" }, + "prE;": { "p": [10931], "c": "\u2AB3" }, + "prap;": { "p": [10935], "c": "\u2AB7" }, + "prcue;": { "p": [8828], "c": "\u227C" }, + "pre;": { "p": [10927], "c": "\u2AAF" }, + "prec;": { "p": [8826], "c": "\u227A" }, + "precapprox;": { "p": [10935], "c": "\u2AB7" }, + "preccurlyeq;": { "p": [8828], "c": "\u227C" }, + "preceq;": { "p": [10927], "c": "\u2AAF" }, + "precnapprox;": { "p": [10937], "c": "\u2AB9" }, + "precneqq;": { "p": [10933], "c": "\u2AB5" }, + "precnsim;": { "p": [8936], "c": "\u22E8" }, + "precsim;": { "p": [8830], "c": "\u227E" }, + "prime;": { "p": [8242], "c": "\u2032" }, + "primes;": { "p": [8473], "c": "\u2119" }, + "prnE;": { "p": [10933], "c": "\u2AB5" }, + "prnap;": { "p": [10937], "c": "\u2AB9" }, + "prnsim;": { "p": [8936], "c": "\u22E8" }, + "prod;": { "p": [8719], "c": "\u220F" }, + "profalar;": { "p": [9006], "c": "\u232E" }, + "profline;": { "p": [8978], "c": "\u2312" }, + "profsurf;": { "p": [8979], "c": "\u2313" }, + "prop;": { "p": [8733], "c": "\u221D" }, + "propto;": { "p": [8733], "c": "\u221D" }, + "prsim;": { "p": [8830], "c": "\u227E" }, + "prurel;": { "p": [8880], "c": "\u22B0" }, + "pscr;": { "p": [120005], "c": "\uD835\uDCC5" }, + "psi;": { "p": [968], "c": "\u03C8" }, + "puncsp;": { "p": [8200], "c": "\u2008" }, + "qfr;": { "p": [120110], "c": "\uD835\uDD2E" }, + "qint;": { "p": [10764], "c": "\u2A0C" }, + "qopf;": { "p": [120162], "c": "\uD835\uDD62" }, + "qprime;": { "p": [8279], "c": "\u2057" }, + "qscr;": { "p": [120006], "c": "\uD835\uDCC6" }, + "quaternions;": { "p": [8461], "c": "\u210D" }, + "quatint;": { "p": [10774], "c": "\u2A16" }, + "quest;": { "p": [63], "c": "\u003F" }, + "questeq;": { "p": [8799], "c": "\u225F" }, + "quot": { "p": [34], "c": "\u0022" }, + "quot;": { "p": [34], "c": "\u0022" }, + "rAarr;": { "p": [8667], "c": "\u21DB" }, + "rArr;": { "p": [8658], "c": "\u21D2" }, + "rAtail;": { "p": [10524], "c": "\u291C" }, + "rBarr;": { "p": [10511], "c": "\u290F" }, + "rHar;": { "p": [10596], "c": "\u2964" }, + "race;": { "p": [8765, 817], "c": "\u223D\u0331" }, + "racute;": { "p": [341], "c": "\u0155" }, + "radic;": { "p": [8730], "c": "\u221A" }, + "raemptyv;": { "p": [10675], "c": "\u29B3" }, + "rang;": { "p": [10217], "c": "\u27E9" }, + "rangd;": { "p": [10642], "c": "\u2992" }, + "range;": { "p": [10661], "c": "\u29A5" }, + "rangle;": { "p": [10217], "c": "\u27E9" }, + "raquo": { "p": [187], "c": "\u00BB" }, + "raquo;": { "p": [187], "c": "\u00BB" }, + "rarr;": { "p": [8594], "c": "\u2192" }, + "rarrap;": { "p": [10613], "c": "\u2975" }, + "rarrb;": { "p": [8677], "c": "\u21E5" }, + "rarrbfs;": { "p": [10528], "c": "\u2920" }, + "rarrc;": { "p": [10547], "c": "\u2933" }, + "rarrfs;": { "p": [10526], "c": "\u291E" }, + "rarrhk;": { "p": [8618], "c": "\u21AA" }, + "rarrlp;": { "p": [8620], "c": "\u21AC" }, + "rarrpl;": { "p": [10565], "c": "\u2945" }, + "rarrsim;": { "p": [10612], "c": "\u2974" }, + "rarrtl;": { "p": [8611], "c": "\u21A3" }, + "rarrw;": { "p": [8605], "c": "\u219D" }, + "ratail;": { "p": [10522], "c": "\u291A" }, + "ratio;": { "p": [8758], "c": "\u2236" }, + "rationals;": { "p": [8474], "c": "\u211A" }, + "rbarr;": { "p": [10509], "c": "\u290D" }, + "rbbrk;": { "p": [10099], "c": "\u2773" }, + "rbrace;": { "p": [125], "c": "\u007D" }, + "rbrack;": { "p": [93], "c": "\u005D" }, + "rbrke;": { "p": [10636], "c": "\u298C" }, + "rbrksld;": { "p": [10638], "c": "\u298E" }, + "rbrkslu;": { "p": [10640], "c": "\u2990" }, + "rcaron;": { "p": [345], "c": "\u0159" }, + "rcedil;": { "p": [343], "c": "\u0157" }, + "rceil;": { "p": [8969], "c": "\u2309" }, + "rcub;": { "p": [125], "c": "\u007D" }, + "rcy;": { "p": [1088], "c": "\u0440" }, + "rdca;": { "p": [10551], "c": "\u2937" }, + "rdldhar;": { "p": [10601], "c": "\u2969" }, + "rdquo;": { "p": [8221], "c": "\u201D" }, + "rdquor;": { "p": [8221], "c": "\u201D" }, + "rdsh;": { "p": [8627], "c": "\u21B3" }, + "real;": { "p": [8476], "c": "\u211C" }, + "realine;": { "p": [8475], "c": "\u211B" }, + "realpart;": { "p": [8476], "c": "\u211C" }, + "reals;": { "p": [8477], "c": "\u211D" }, + "rect;": { "p": [9645], "c": "\u25AD" }, + "reg": { "p": [174], "c": "\u00AE" }, + "reg;": { "p": [174], "c": "\u00AE" }, + "rfisht;": { "p": [10621], "c": "\u297D" }, + "rfloor;": { "p": [8971], "c": "\u230B" }, + "rfr;": { "p": [120111], "c": "\uD835\uDD2F" }, + "rhard;": { "p": [8641], "c": "\u21C1" }, + "rharu;": { "p": [8640], "c": "\u21C0" }, + "rharul;": { "p": [10604], "c": "\u296C" }, + "rho;": { "p": [961], "c": "\u03C1" }, + "rhov;": { "p": [1009], "c": "\u03F1" }, + "rightarrow;": { "p": [8594], "c": "\u2192" }, + "rightarrowtail;": { "p": [8611], "c": "\u21A3" }, + "rightharpoondown;": { "p": [8641], "c": "\u21C1" }, + "rightharpoonup;": { "p": [8640], "c": "\u21C0" }, + "rightleftarrows;": { "p": [8644], "c": "\u21C4" }, + "rightleftharpoons;": { "p": [8652], "c": "\u21CC" }, + "rightrightarrows;": { "p": [8649], "c": "\u21C9" }, + "rightsquigarrow;": { "p": [8605], "c": "\u219D" }, + "rightthreetimes;": { "p": [8908], "c": "\u22CC" }, + "ring;": { "p": [730], "c": "\u02DA" }, + "risingdotseq;": { "p": [8787], "c": "\u2253" }, + "rlarr;": { "p": [8644], "c": "\u21C4" }, + "rlhar;": { "p": [8652], "c": "\u21CC" }, + "rlm;": { "p": [8207], "c": "\u200F" }, + "rmoust;": { "p": [9137], "c": "\u23B1" }, + "rmoustache;": { "p": [9137], "c": "\u23B1" }, + "rnmid;": { "p": [10990], "c": "\u2AEE" }, + "roang;": { "p": [10221], "c": "\u27ED" }, + "roarr;": { "p": [8702], "c": "\u21FE" }, + "robrk;": { "p": [10215], "c": "\u27E7" }, + "ropar;": { "p": [10630], "c": "\u2986" }, + "ropf;": { "p": [120163], "c": "\uD835\uDD63" }, + "roplus;": { "p": [10798], "c": "\u2A2E" }, + "rotimes;": { "p": [10805], "c": "\u2A35" }, + "rpar;": { "p": [41], "c": "\u0029" }, + "rpargt;": { "p": [10644], "c": "\u2994" }, + "rppolint;": { "p": [10770], "c": "\u2A12" }, + "rrarr;": { "p": [8649], "c": "\u21C9" }, + "rsaquo;": { "p": [8250], "c": "\u203A" }, + "rscr;": { "p": [120007], "c": "\uD835\uDCC7" }, + "rsh;": { "p": [8625], "c": "\u21B1" }, + "rsqb;": { "p": [93], "c": "\u005D" }, + "rsquo;": { "p": [8217], "c": "\u2019" }, + "rsquor;": { "p": [8217], "c": "\u2019" }, + "rthree;": { "p": [8908], "c": "\u22CC" }, + "rtimes;": { "p": [8906], "c": "\u22CA" }, + "rtri;": { "p": [9657], "c": "\u25B9" }, + "rtrie;": { "p": [8885], "c": "\u22B5" }, + "rtrif;": { "p": [9656], "c": "\u25B8" }, + "rtriltri;": { "p": [10702], "c": "\u29CE" }, + "ruluhar;": { "p": [10600], "c": "\u2968" }, + "rx;": { "p": [8478], "c": "\u211E" }, + "sacute;": { "p": [347], "c": "\u015B" }, + "sbquo;": { "p": [8218], "c": "\u201A" }, + "sc;": { "p": [8827], "c": "\u227B" }, + "scE;": { "p": [10932], "c": "\u2AB4" }, + "scap;": { "p": [10936], "c": "\u2AB8" }, + "scaron;": { "p": [353], "c": "\u0161" }, + "sccue;": { "p": [8829], "c": "\u227D" }, + "sce;": { "p": [10928], "c": "\u2AB0" }, + "scedil;": { "p": [351], "c": "\u015F" }, + "scirc;": { "p": [349], "c": "\u015D" }, + "scnE;": { "p": [10934], "c": "\u2AB6" }, + "scnap;": { "p": [10938], "c": "\u2ABA" }, + "scnsim;": { "p": [8937], "c": "\u22E9" }, + "scpolint;": { "p": [10771], "c": "\u2A13" }, + "scsim;": { "p": [8831], "c": "\u227F" }, + "scy;": { "p": [1089], "c": "\u0441" }, + "sdot;": { "p": [8901], "c": "\u22C5" }, + "sdotb;": { "p": [8865], "c": "\u22A1" }, + "sdote;": { "p": [10854], "c": "\u2A66" }, + "seArr;": { "p": [8664], "c": "\u21D8" }, + "searhk;": { "p": [10533], "c": "\u2925" }, + "searr;": { "p": [8600], "c": "\u2198" }, + "searrow;": { "p": [8600], "c": "\u2198" }, + "sect": { "p": [167], "c": "\u00A7" }, + "sect;": { "p": [167], "c": "\u00A7" }, + "semi;": { "p": [59], "c": "\u003B" }, + "seswar;": { "p": [10537], "c": "\u2929" }, + "setminus;": { "p": [8726], "c": "\u2216" }, + "setmn;": { "p": [8726], "c": "\u2216" }, + "sext;": { "p": [10038], "c": "\u2736" }, + "sfr;": { "p": [120112], "c": "\uD835\uDD30" }, + "sfrown;": { "p": [8994], "c": "\u2322" }, + "sharp;": { "p": [9839], "c": "\u266F" }, + "shchcy;": { "p": [1097], "c": "\u0449" }, + "shcy;": { "p": [1096], "c": "\u0448" }, + "shortmid;": { "p": [8739], "c": "\u2223" }, + "shortparallel;": { "p": [8741], "c": "\u2225" }, + "shy": { "p": [173], "c": "\u00AD" }, + "shy;": { "p": [173], "c": "\u00AD" }, + "sigma;": { "p": [963], "c": "\u03C3" }, + "sigmaf;": { "p": [962], "c": "\u03C2" }, + "sigmav;": { "p": [962], "c": "\u03C2" }, + "sim;": { "p": [8764], "c": "\u223C" }, + "simdot;": { "p": [10858], "c": "\u2A6A" }, + "sime;": { "p": [8771], "c": "\u2243" }, + "simeq;": { "p": [8771], "c": "\u2243" }, + "simg;": { "p": [10910], "c": "\u2A9E" }, + "simgE;": { "p": [10912], "c": "\u2AA0" }, + "siml;": { "p": [10909], "c": "\u2A9D" }, + "simlE;": { "p": [10911], "c": "\u2A9F" }, + "simne;": { "p": [8774], "c": "\u2246" }, + "simplus;": { "p": [10788], "c": "\u2A24" }, + "simrarr;": { "p": [10610], "c": "\u2972" }, + "slarr;": { "p": [8592], "c": "\u2190" }, + "smallsetminus;": { "p": [8726], "c": "\u2216" }, + "smashp;": { "p": [10803], "c": "\u2A33" }, + "smeparsl;": { "p": [10724], "c": "\u29E4" }, + "smid;": { "p": [8739], "c": "\u2223" }, + "smile;": { "p": [8995], "c": "\u2323" }, + "smt;": { "p": [10922], "c": "\u2AAA" }, + "smte;": { "p": [10924], "c": "\u2AAC" }, + "smtes;": { "p": [10924, 65024], "c": "\u2AAC\uFE00" }, + "softcy;": { "p": [1100], "c": "\u044C" }, + "sol;": { "p": [47], "c": "\u002F" }, + "solb;": { "p": [10692], "c": "\u29C4" }, + "solbar;": { "p": [9023], "c": "\u233F" }, + "sopf;": { "p": [120164], "c": "\uD835\uDD64" }, + "spades;": { "p": [9824], "c": "\u2660" }, + "spadesuit;": { "p": [9824], "c": "\u2660" }, + "spar;": { "p": [8741], "c": "\u2225" }, + "sqcap;": { "p": [8851], "c": "\u2293" }, + "sqcaps;": { "p": [8851, 65024], "c": "\u2293\uFE00" }, + "sqcup;": { "p": [8852], "c": "\u2294" }, + "sqcups;": { "p": [8852, 65024], "c": "\u2294\uFE00" }, + "sqsub;": { "p": [8847], "c": "\u228F" }, + "sqsube;": { "p": [8849], "c": "\u2291" }, + "sqsubset;": { "p": [8847], "c": "\u228F" }, + "sqsubseteq;": { "p": [8849], "c": "\u2291" }, + "sqsup;": { "p": [8848], "c": "\u2290" }, + "sqsupe;": { "p": [8850], "c": "\u2292" }, + "sqsupset;": { "p": [8848], "c": "\u2290" }, + "sqsupseteq;": { "p": [8850], "c": "\u2292" }, + "squ;": { "p": [9633], "c": "\u25A1" }, + "square;": { "p": [9633], "c": "\u25A1" }, + "squarf;": { "p": [9642], "c": "\u25AA" }, + "squf;": { "p": [9642], "c": "\u25AA" }, + "srarr;": { "p": [8594], "c": "\u2192" }, + "sscr;": { "p": [120008], "c": "\uD835\uDCC8" }, + "ssetmn;": { "p": [8726], "c": "\u2216" }, + "ssmile;": { "p": [8995], "c": "\u2323" }, + "sstarf;": { "p": [8902], "c": "\u22C6" }, + "star;": { "p": [9734], "c": "\u2606" }, + "starf;": { "p": [9733], "c": "\u2605" }, + "straightepsilon;": { "p": [1013], "c": "\u03F5" }, + "straightphi;": { "p": [981], "c": "\u03D5" }, + "strns;": { "p": [175], "c": "\u00AF" }, + "sub;": { "p": [8834], "c": "\u2282" }, + "subE;": { "p": [10949], "c": "\u2AC5" }, + "subdot;": { "p": [10941], "c": "\u2ABD" }, + "sube;": { "p": [8838], "c": "\u2286" }, + "subedot;": { "p": [10947], "c": "\u2AC3" }, + "submult;": { "p": [10945], "c": "\u2AC1" }, + "subnE;": { "p": [10955], "c": "\u2ACB" }, + "subne;": { "p": [8842], "c": "\u228A" }, + "subplus;": { "p": [10943], "c": "\u2ABF" }, + "subrarr;": { "p": [10617], "c": "\u2979" }, + "subset;": { "p": [8834], "c": "\u2282" }, + "subseteq;": { "p": [8838], "c": "\u2286" }, + "subseteqq;": { "p": [10949], "c": "\u2AC5" }, + "subsetneq;": { "p": [8842], "c": "\u228A" }, + "subsetneqq;": { "p": [10955], "c": "\u2ACB" }, + "subsim;": { "p": [10951], "c": "\u2AC7" }, + "subsub;": { "p": [10965], "c": "\u2AD5" }, + "subsup;": { "p": [10963], "c": "\u2AD3" }, + "succ;": { "p": [8827], "c": "\u227B" }, + "succapprox;": { "p": [10936], "c": "\u2AB8" }, + "succcurlyeq;": { "p": [8829], "c": "\u227D" }, + "succeq;": { "p": [10928], "c": "\u2AB0" }, + "succnapprox;": { "p": [10938], "c": "\u2ABA" }, + "succneqq;": { "p": [10934], "c": "\u2AB6" }, + "succnsim;": { "p": [8937], "c": "\u22E9" }, + "succsim;": { "p": [8831], "c": "\u227F" }, + "sum;": { "p": [8721], "c": "\u2211" }, + "sung;": { "p": [9834], "c": "\u266A" }, + "sup1": { "p": [185], "c": "\u00B9" }, + "sup1;": { "p": [185], "c": "\u00B9" }, + "sup2": { "p": [178], "c": "\u00B2" }, + "sup2;": { "p": [178], "c": "\u00B2" }, + "sup3": { "p": [179], "c": "\u00B3" }, + "sup3;": { "p": [179], "c": "\u00B3" }, + "sup;": { "p": [8835], "c": "\u2283" }, + "supE;": { "p": [10950], "c": "\u2AC6" }, + "supdot;": { "p": [10942], "c": "\u2ABE" }, + "supdsub;": { "p": [10968], "c": "\u2AD8" }, + "supe;": { "p": [8839], "c": "\u2287" }, + "supedot;": { "p": [10948], "c": "\u2AC4" }, + "suphsol;": { "p": [10185], "c": "\u27C9" }, + "suphsub;": { "p": [10967], "c": "\u2AD7" }, + "suplarr;": { "p": [10619], "c": "\u297B" }, + "supmult;": { "p": [10946], "c": "\u2AC2" }, + "supnE;": { "p": [10956], "c": "\u2ACC" }, + "supne;": { "p": [8843], "c": "\u228B" }, + "supplus;": { "p": [10944], "c": "\u2AC0" }, + "supset;": { "p": [8835], "c": "\u2283" }, + "supseteq;": { "p": [8839], "c": "\u2287" }, + "supseteqq;": { "p": [10950], "c": "\u2AC6" }, + "supsetneq;": { "p": [8843], "c": "\u228B" }, + "supsetneqq;": { "p": [10956], "c": "\u2ACC" }, + "supsim;": { "p": [10952], "c": "\u2AC8" }, + "supsub;": { "p": [10964], "c": "\u2AD4" }, + "supsup;": { "p": [10966], "c": "\u2AD6" }, + "swArr;": { "p": [8665], "c": "\u21D9" }, + "swarhk;": { "p": [10534], "c": "\u2926" }, + "swarr;": { "p": [8601], "c": "\u2199" }, + "swarrow;": { "p": [8601], "c": "\u2199" }, + "swnwar;": { "p": [10538], "c": "\u292A" }, + "szlig": { "p": [223], "c": "\u00DF" }, + "szlig;": { "p": [223], "c": "\u00DF" }, + "target;": { "p": [8982], "c": "\u2316" }, + "tau;": { "p": [964], "c": "\u03C4" }, + "tbrk;": { "p": [9140], "c": "\u23B4" }, + "tcaron;": { "p": [357], "c": "\u0165" }, + "tcedil;": { "p": [355], "c": "\u0163" }, + "tcy;": { "p": [1090], "c": "\u0442" }, + "tdot;": { "p": [8411], "c": "\u20DB" }, + "telrec;": { "p": [8981], "c": "\u2315" }, + "tfr;": { "p": [120113], "c": "\uD835\uDD31" }, + "there4;": { "p": [8756], "c": "\u2234" }, + "therefore;": { "p": [8756], "c": "\u2234" }, + "theta;": { "p": [952], "c": "\u03B8" }, + "thetasym;": { "p": [977], "c": "\u03D1" }, + "thetav;": { "p": [977], "c": "\u03D1" }, + "thickapprox;": { "p": [8776], "c": "\u2248" }, + "thicksim;": { "p": [8764], "c": "\u223C" }, + "thinsp;": { "p": [8201], "c": "\u2009" }, + "thkap;": { "p": [8776], "c": "\u2248" }, + "thksim;": { "p": [8764], "c": "\u223C" }, + "thorn": { "p": [254], "c": "\u00FE" }, + "thorn;": { "p": [254], "c": "\u00FE" }, + "tilde;": { "p": [732], "c": "\u02DC" }, + "times": { "p": [215], "c": "\u00D7" }, + "times;": { "p": [215], "c": "\u00D7" }, + "timesb;": { "p": [8864], "c": "\u22A0" }, + "timesbar;": { "p": [10801], "c": "\u2A31" }, + "timesd;": { "p": [10800], "c": "\u2A30" }, + "tint;": { "p": [8749], "c": "\u222D" }, + "toea;": { "p": [10536], "c": "\u2928" }, + "top;": { "p": [8868], "c": "\u22A4" }, + "topbot;": { "p": [9014], "c": "\u2336" }, + "topcir;": { "p": [10993], "c": "\u2AF1" }, + "topf;": { "p": [120165], "c": "\uD835\uDD65" }, + "topfork;": { "p": [10970], "c": "\u2ADA" }, + "tosa;": { "p": [10537], "c": "\u2929" }, + "tprime;": { "p": [8244], "c": "\u2034" }, + "trade;": { "p": [8482], "c": "\u2122" }, + "triangle;": { "p": [9653], "c": "\u25B5" }, + "triangledown;": { "p": [9663], "c": "\u25BF" }, + "triangleleft;": { "p": [9667], "c": "\u25C3" }, + "trianglelefteq;": { "p": [8884], "c": "\u22B4" }, + "triangleq;": { "p": [8796], "c": "\u225C" }, + "triangleright;": { "p": [9657], "c": "\u25B9" }, + "trianglerighteq;": { "p": [8885], "c": "\u22B5" }, + "tridot;": { "p": [9708], "c": "\u25EC" }, + "trie;": { "p": [8796], "c": "\u225C" }, + "triminus;": { "p": [10810], "c": "\u2A3A" }, + "triplus;": { "p": [10809], "c": "\u2A39" }, + "trisb;": { "p": [10701], "c": "\u29CD" }, + "tritime;": { "p": [10811], "c": "\u2A3B" }, + "trpezium;": { "p": [9186], "c": "\u23E2" }, + "tscr;": { "p": [120009], "c": "\uD835\uDCC9" }, + "tscy;": { "p": [1094], "c": "\u0446" }, + "tshcy;": { "p": [1115], "c": "\u045B" }, + "tstrok;": { "p": [359], "c": "\u0167" }, + "twixt;": { "p": [8812], "c": "\u226C" }, + "twoheadleftarrow;": { "p": [8606], "c": "\u219E" }, + "twoheadrightarrow;": { "p": [8608], "c": "\u21A0" }, + "uArr;": { "p": [8657], "c": "\u21D1" }, + "uHar;": { "p": [10595], "c": "\u2963" }, + "uacute": { "p": [250], "c": "\u00FA" }, + "uacute;": { "p": [250], "c": "\u00FA" }, + "uarr;": { "p": [8593], "c": "\u2191" }, + "ubrcy;": { "p": [1118], "c": "\u045E" }, + "ubreve;": { "p": [365], "c": "\u016D" }, + "ucirc": { "p": [251], "c": "\u00FB" }, + "ucirc;": { "p": [251], "c": "\u00FB" }, + "ucy;": { "p": [1091], "c": "\u0443" }, + "udarr;": { "p": [8645], "c": "\u21C5" }, + "udblac;": { "p": [369], "c": "\u0171" }, + "udhar;": { "p": [10606], "c": "\u296E" }, + "ufisht;": { "p": [10622], "c": "\u297E" }, + "ufr;": { "p": [120114], "c": "\uD835\uDD32" }, + "ugrave": { "p": [249], "c": "\u00F9" }, + "ugrave;": { "p": [249], "c": "\u00F9" }, + "uharl;": { "p": [8639], "c": "\u21BF" }, + "uharr;": { "p": [8638], "c": "\u21BE" }, + "uhblk;": { "p": [9600], "c": "\u2580" }, + "ulcorn;": { "p": [8988], "c": "\u231C" }, + "ulcorner;": { "p": [8988], "c": "\u231C" }, + "ulcrop;": { "p": [8975], "c": "\u230F" }, + "ultri;": { "p": [9720], "c": "\u25F8" }, + "umacr;": { "p": [363], "c": "\u016B" }, + "uml": { "p": [168], "c": "\u00A8" }, + "uml;": { "p": [168], "c": "\u00A8" }, + "uogon;": { "p": [371], "c": "\u0173" }, + "uopf;": { "p": [120166], "c": "\uD835\uDD66" }, + "uparrow;": { "p": [8593], "c": "\u2191" }, + "updownarrow;": { "p": [8597], "c": "\u2195" }, + "upharpoonleft;": { "p": [8639], "c": "\u21BF" }, + "upharpoonright;": { "p": [8638], "c": "\u21BE" }, + "uplus;": { "p": [8846], "c": "\u228E" }, + "upsi;": { "p": [965], "c": "\u03C5" }, + "upsih;": { "p": [978], "c": "\u03D2" }, + "upsilon;": { "p": [965], "c": "\u03C5" }, + "upuparrows;": { "p": [8648], "c": "\u21C8" }, + "urcorn;": { "p": [8989], "c": "\u231D" }, + "urcorner;": { "p": [8989], "c": "\u231D" }, + "urcrop;": { "p": [8974], "c": "\u230E" }, + "uring;": { "p": [367], "c": "\u016F" }, + "urtri;": { "p": [9721], "c": "\u25F9" }, + "uscr;": { "p": [120010], "c": "\uD835\uDCCA" }, + "utdot;": { "p": [8944], "c": "\u22F0" }, + "utilde;": { "p": [361], "c": "\u0169" }, + "utri;": { "p": [9653], "c": "\u25B5" }, + "utrif;": { "p": [9652], "c": "\u25B4" }, + "uuarr;": { "p": [8648], "c": "\u21C8" }, + "uuml": { "p": [252], "c": "\u00FC" }, + "uuml;": { "p": [252], "c": "\u00FC" }, + "uwangle;": { "p": [10663], "c": "\u29A7" }, + "vArr;": { "p": [8661], "c": "\u21D5" }, + "vBar;": { "p": [10984], "c": "\u2AE8" }, + "vBarv;": { "p": [10985], "c": "\u2AE9" }, + "vDash;": { "p": [8872], "c": "\u22A8" }, + "vangrt;": { "p": [10652], "c": "\u299C" }, + "varepsilon;": { "p": [1013], "c": "\u03F5" }, + "varkappa;": { "p": [1008], "c": "\u03F0" }, + "varnothing;": { "p": [8709], "c": "\u2205" }, + "varphi;": { "p": [981], "c": "\u03D5" }, + "varpi;": { "p": [982], "c": "\u03D6" }, + "varpropto;": { "p": [8733], "c": "\u221D" }, + "varr;": { "p": [8597], "c": "\u2195" }, + "varrho;": { "p": [1009], "c": "\u03F1" }, + "varsigma;": { "p": [962], "c": "\u03C2" }, + "varsubsetneq;": { + "p": [8842, 65024], + "c": "\u228A\uFE00" + }, + "varsubsetneqq;": { + "p": [10955, 65024], + "c": "\u2ACB\uFE00" + }, + "varsupsetneq;": { + "p": [8843, 65024], + "c": "\u228B\uFE00" + }, + "varsupsetneqq;": { + "p": [10956, 65024], + "c": "\u2ACC\uFE00" + }, + "vartheta;": { "p": [977], "c": "\u03D1" }, + "vartriangleleft;": { "p": [8882], "c": "\u22B2" }, + "vartriangleright;": { "p": [8883], "c": "\u22B3" }, + "vcy;": { "p": [1074], "c": "\u0432" }, + "vdash;": { "p": [8866], "c": "\u22A2" }, + "vee;": { "p": [8744], "c": "\u2228" }, + "veebar;": { "p": [8891], "c": "\u22BB" }, + "veeeq;": { "p": [8794], "c": "\u225A" }, + "vellip;": { "p": [8942], "c": "\u22EE" }, + "verbar;": { "p": [124], "c": "\u007C" }, + "vert;": { "p": [124], "c": "\u007C" }, + "vfr;": { "p": [120115], "c": "\uD835\uDD33" }, + "vltri;": { "p": [8882], "c": "\u22B2" }, + "vnsub;": { "p": [8834, 8402], "c": "\u2282\u20D2" }, + "vnsup;": { "p": [8835, 8402], "c": "\u2283\u20D2" }, + "vopf;": { "p": [120167], "c": "\uD835\uDD67" }, + "vprop;": { "p": [8733], "c": "\u221D" }, + "vrtri;": { "p": [8883], "c": "\u22B3" }, + "vscr;": { "p": [120011], "c": "\uD835\uDCCB" }, + "vsubnE;": { "p": [10955, 65024], "c": "\u2ACB\uFE00" }, + "vsubne;": { "p": [8842, 65024], "c": "\u228A\uFE00" }, + "vsupnE;": { "p": [10956, 65024], "c": "\u2ACC\uFE00" }, + "vsupne;": { "p": [8843, 65024], "c": "\u228B\uFE00" }, + "vzigzag;": { "p": [10650], "c": "\u299A" }, + "wcirc;": { "p": [373], "c": "\u0175" }, + "wedbar;": { "p": [10847], "c": "\u2A5F" }, + "wedge;": { "p": [8743], "c": "\u2227" }, + "wedgeq;": { "p": [8793], "c": "\u2259" }, + "weierp;": { "p": [8472], "c": "\u2118" }, + "wfr;": { "p": [120116], "c": "\uD835\uDD34" }, + "wopf;": { "p": [120168], "c": "\uD835\uDD68" }, + "wp;": { "p": [8472], "c": "\u2118" }, + "wr;": { "p": [8768], "c": "\u2240" }, + "wreath;": { "p": [8768], "c": "\u2240" }, + "wscr;": { "p": [120012], "c": "\uD835\uDCCC" }, + "xcap;": { "p": [8898], "c": "\u22C2" }, + "xcirc;": { "p": [9711], "c": "\u25EF" }, + "xcup;": { "p": [8899], "c": "\u22C3" }, + "xdtri;": { "p": [9661], "c": "\u25BD" }, + "xfr;": { "p": [120117], "c": "\uD835\uDD35" }, + "xhArr;": { "p": [10234], "c": "\u27FA" }, + "xharr;": { "p": [10231], "c": "\u27F7" }, + "xi;": { "p": [958], "c": "\u03BE" }, + "xlArr;": { "p": [10232], "c": "\u27F8" }, + "xlarr;": { "p": [10229], "c": "\u27F5" }, + "xmap;": { "p": [10236], "c": "\u27FC" }, + "xnis;": { "p": [8955], "c": "\u22FB" }, + "xodot;": { "p": [10752], "c": "\u2A00" }, + "xopf;": { "p": [120169], "c": "\uD835\uDD69" }, + "xoplus;": { "p": [10753], "c": "\u2A01" }, + "xotime;": { "p": [10754], "c": "\u2A02" }, + "xrArr;": { "p": [10233], "c": "\u27F9" }, + "xrarr;": { "p": [10230], "c": "\u27F6" }, + "xscr;": { "p": [120013], "c": "\uD835\uDCCD" }, + "xsqcup;": { "p": [10758], "c": "\u2A06" }, + "xuplus;": { "p": [10756], "c": "\u2A04" }, + "xutri;": { "p": [9651], "c": "\u25B3" }, + "xvee;": { "p": [8897], "c": "\u22C1" }, + "xwedge;": { "p": [8896], "c": "\u22C0" }, + "yacute": { "p": [253], "c": "\u00FD" }, + "yacute;": { "p": [253], "c": "\u00FD" }, + "yacy;": { "p": [1103], "c": "\u044F" }, + "ycirc;": { "p": [375], "c": "\u0177" }, + "ycy;": { "p": [1099], "c": "\u044B" }, + "yen": { "p": [165], "c": "\u00A5" }, + "yen;": { "p": [165], "c": "\u00A5" }, + "yfr;": { "p": [120118], "c": "\uD835\uDD36" }, + "yicy;": { "p": [1111], "c": "\u0457" }, + "yopf;": { "p": [120170], "c": "\uD835\uDD6A" }, + "yscr;": { "p": [120014], "c": "\uD835\uDCCE" }, + "yucy;": { "p": [1102], "c": "\u044E" }, + "yuml": { "p": [255], "c": "\u00FF" }, + "yuml;": { "p": [255], "c": "\u00FF" }, + "zacute;": { "p": [378], "c": "\u017A" }, + "zcaron;": { "p": [382], "c": "\u017E" }, + "zcy;": { "p": [1079], "c": "\u0437" }, + "zdot;": { "p": [380], "c": "\u017C" }, + "zeetrf;": { "p": [8488], "c": "\u2128" }, + "zeta;": { "p": [950], "c": "\u03B6" }, + "zfr;": { "p": [120119], "c": "\uD835\uDD37" }, + "zhcy;": { "p": [1078], "c": "\u0436" }, + "zigrarr;": { "p": [8669], "c": "\u21DD" }, + "zopf;": { "p": [120171], "c": "\uD835\uDD6B" }, + "zscr;": { "p": [120015], "c": "\uD835\uDCCF" }, + "zwj;": { "p": [8205], "c": "\u200D" }, + "zwnj;": { "p": [8204], "c": "\u200C" } } diff --git a/src/lib/utils/html.ts b/src/lib/utils/html.ts index 42582524e..ed983a8c2 100644 --- a/src/lib/utils/html.ts +++ b/src/lib/utils/html.ts @@ -1,30 +1,29 @@ // There is a fixed list of named character references which will not be expanded in the future. // This json file is based on https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references -// with some modifications to reduce the file size of the original JSON since we just need. +// with some modifications to reduce the file size of the original JSON. +import { assertNever } from "./general"; import htmlEntities from "./html-entities.json"; -// Three cases: -// { - numeric escape -//  - hex escape -// & - named escape -function unescapeEntities(html: string) { - return html.replace( - /&(#(?:\d+);?|(?:#[xX][0-9A-Fa-f]+);?|(?:\w+);?)/g, - (_, n) => { - if (n[0] === "#") { - return String.fromCharCode( - n[1] === "x" || n[1] === "X" - ? parseInt(n.substring(2), 16) - : parseInt(n.substring(1), 10), - ); - } - return htmlEntities[n as never] || ""; - }, - ); +interface EntityData { + /** code points associated with this escape */ + p: number[]; + /** String representing this escape when rendered */ + c: string; } -export function getTextContent(text: string) { - return unescapeEntities(text.replace(/<.*?(?:>|$)/g, "")); +// https://en.wikipedia.org/wiki/Trie +interface Trie { + data?: EntityData; + children?: Record<number, Trie | undefined>; +} +const htmlEntitiesTrie: Trie = {}; +for (const [name, data] of Object.entries(htmlEntities)) { + let current = htmlEntitiesTrie; + for (let i = 0; i < name.length; ++i) { + current.children ||= {}; + current = current.children[name.charCodeAt(i)] ||= {}; + } + current.data = data; } const htmlEscapes: Record<string, string> = { @@ -38,3 +37,604 @@ const htmlEscapes: Record<string, string> = { export function escapeHtml(html: string) { return html.replace(/[&<>'"]/g, (c) => htmlEscapes[c as never]); } + +/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ +const enum Chars { + EOF = -1, + NULL = 0, + TAB = 0x9, + LF = 0xa, + FF = 0xc, + SPACE = 0x20, + NUMBER_SIGN = 0x23, + SOLIDUS = 0x2f, + QUOTATION_MARK = 0x22, + AMPERSAND = 0x26, + APOTROPHE = 0x27, + SEMICOLON = 0x3b, + ZERO = 0x30, + NINE = 0x39, + LESS_THAN = 0x3c, + EQUALS = 0x3d, + GREATER_THAN = 0x3e, + UPPERCASE_A = 0x41, + UPPERCASE_F = 0x46, + UPPERCASE_X = 0x58, + UPPERCASE_Z = 0x5a, + GRAVE_ACCENT = 0x60, + LOWERCASE_A = 0x61, + LOWERCASE_F = 0x66, + LOWERCASE_X = 0x78, + LOWERCASE_Z = 0x7a, +} + +function isalpha(ch: number) { + return Chars.LOWERCASE_A <= (ch | 0x20) && (ch | 0x20) <= Chars.LOWERCASE_Z; +} + +function isdigit(ch: number) { + return Chars.ZERO <= ch && ch <= Chars.NINE; +} + +function isalnum(ch: number) { + return isalpha(ch) || isdigit(ch); +} + +function isxdigit(ch: number) { + return ( + isdigit(ch) || + (Chars.LOWERCASE_A <= (ch | 0x20) && (ch | 0x20) <= Chars.LOWERCASE_F) + ); +} + +export const enum ParserState { + BeforeAttributeName, + AfterAttributeName, + BeforeAttributeValue, + END, +} + +/** + * Parser for HTML attributes, each call to {@link step} will + * pause the parser at key points used to extract relative links from markdown + * + * The parser will pause at the points marked with `^`: + * + * ```text + * attr="value" attr='value' attr=value attr attr2 /> + * ^ ^ ^ ^ ^ ^ ^ ^^ + * BeforeValue | | | | | || + * BeforeName | | | | || + * BeforeValue | | | || + * BeforeName | | || + * BeforeValue| || + * BeforeName || + * AfterName + * AfterName + * END + * ``` + */ +export class HtmlAttributeParser { + state = ParserState.BeforeAttributeName; + currentAttributeName: string = ""; + currentAttributeValueStart = -1; + currentAttributeValueEnd = -1; + currentAttributeValue: string = ""; + private temporaryBuffer: number[] = []; + private characterReferenceCode = 0; + + constructor( + readonly text: string, + public pos: number = 0, + ) {} + + step() { + switch (this.state) { + case ParserState.BeforeAttributeName: + this.beforeAttributeName(); + return; + case ParserState.AfterAttributeName: + this.afterAttributeName(); + return; + case ParserState.BeforeAttributeValue: + this.beforeAttributeValue(); + return; + + case ParserState.END: + return; // Do nothing + } + + /* c8 ignore next */ + assertNever(this.state); + } + + private peek() { + const ch = this.text.charCodeAt(this.pos); + return isNaN(ch) ? Chars.EOF : ch; + } + private consume() { + const ch = this.peek(); + ++this.pos; + return ch; + } + + // https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state + beforeAttributeName() { + this.currentAttributeName = ""; + this.currentAttributeValue = ""; + + loop: for (;;) { + switch (this.consume()) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + break; + case Chars.SOLIDUS: + case Chars.GREATER_THAN: + case Chars.EOF: + --this.pos; + this.afterAttributeName(); + break loop; + case Chars.EQUALS: + // Unexpected equals sign before attribute name parse error. + // fall through + default: + --this.pos; + this.attributeName(); + break loop; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state + attributeName() { + const startPos = this.pos; + loop: for (;;) { + const ch = this.consume(); + switch (ch) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + case Chars.SOLIDUS: + case Chars.GREATER_THAN: + case Chars.EOF: + --this.pos; + this.state = ParserState.AfterAttributeName; + break loop; + case Chars.EQUALS: + this.state = ParserState.BeforeAttributeValue; + break loop; + case Chars.QUOTATION_MARK: + case Chars.APOTROPHE: + case Chars.LESS_THAN: + // This is an unexpected-character-in-attribute-name parse error. Treat it as per the "anything else" entry below. + // fall through + default: + // Do nothing, we collect the attribute name after the loop + break; + } + } + + if (this.state === ParserState.BeforeAttributeValue) { + this.currentAttributeName = this.text + .substring(startPos, this.pos - 1) + .toLowerCase(); + } else { + this.currentAttributeName = this.text + .substring(startPos, this.pos) + .toLowerCase(); + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#after-attribute-name-state + afterAttributeName() { + loop: for (;;) { + switch (this.consume()) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + break; // Ignore the character + case Chars.SOLIDUS: + this.state = ParserState.END; // self-closing start tag state + break loop; + case Chars.EQUALS: + this.state = ParserState.BeforeAttributeValue; + break loop; + case Chars.GREATER_THAN: + this.state = ParserState.END; // data state + break loop; + case Chars.EOF: + this.state = ParserState.END; // eof-in-tag parse error + break loop; + default: + --this.pos; + this.attributeName(); + break loop; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-value-state + beforeAttributeValue() { + loop: for (;;) { + switch (this.consume()) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + break; // Ignore the character + case Chars.QUOTATION_MARK: + this.attributeValueDoubleQuoted(); + break loop; + case Chars.APOTROPHE: + this.attributeValueSingleQuoted(); + break loop; + case Chars.GREATER_THAN: + this.state = ParserState.END; // missing-attribute-value parse error + break loop; + default: + --this.pos; + this.attributeValueUnquoted(); + break loop; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state + attributeValueDoubleQuoted() { + this.currentAttributeValueStart = this.pos; + loop: for (;;) { + switch (this.consume()) { + case Chars.QUOTATION_MARK: + this.currentAttributeValueEnd = this.pos - 1; + this.afterAttributeValueQuoted(); + break loop; + case Chars.AMPERSAND: + this.characterReference(); + break; + case Chars.NULL: + this.currentAttributeValue += String.fromCharCode(0xfffd); + break; + case Chars.EOF: + this.currentAttributeValueEnd = this.pos; + this.state = ParserState.END; // eof-in-tag parse error + break loop; + default: + this.currentAttributeValue += this.text[this.pos - 1]; + break; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(single-quoted)-state + attributeValueSingleQuoted() { + this.currentAttributeValueStart = this.pos; + loop: for (;;) { + switch (this.consume()) { + case Chars.APOTROPHE: + this.currentAttributeValueEnd = this.pos - 1; + this.afterAttributeValueQuoted(); + break loop; + case Chars.AMPERSAND: + this.characterReference(); + break; + case Chars.NULL: + this.currentAttributeValue += String.fromCharCode(0xfffd); + break; + case Chars.EOF: + this.currentAttributeValueEnd = this.pos; + this.state = ParserState.END; // eof-in-tag parse error + break loop; + default: + this.currentAttributeValue += this.text[this.pos - 1]; + break; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(unquoted)-state + attributeValueUnquoted() { + this.currentAttributeValueStart = this.pos; + loop: for (;;) { + switch (this.consume()) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + this.currentAttributeValueEnd = this.pos - 1; + this.state = ParserState.BeforeAttributeName; + break loop; + case Chars.AMPERSAND: + this.characterReference(); + break; + case Chars.GREATER_THAN: + this.currentAttributeValueEnd = this.pos; + this.state = ParserState.END; + break loop; + case Chars.NULL: + this.currentAttributeValue += String.fromCharCode(0xfffd); + break; + case Chars.EOF: + this.currentAttributeValueEnd = this.pos; + this.state = ParserState.END; // eof-in-tag parse error + break loop; + case Chars.QUOTATION_MARK: + case Chars.APOTROPHE: + case Chars.LESS_THAN: + case Chars.EQUALS: + case Chars.GRAVE_ACCENT: + // This is an unexpected-character-in-unquoted-attribute-value parse error. Treat it as per the "anything else" entry below. + // fall through + default: + this.currentAttributeValue += this.text[this.pos - 1]; + break; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#after-attribute-value-(quoted)-state + afterAttributeValueQuoted() { + switch (this.consume()) { + case Chars.TAB: + case Chars.LF: + case Chars.FF: + case Chars.SPACE: + this.state = ParserState.BeforeAttributeName; + break; + case Chars.SOLIDUS: + case Chars.GREATER_THAN: + case Chars.EOF: + this.state = ParserState.END; + break; + default: + // This is a missing-whitespace-between-attributes parse error. Reconsume in the before attribute name state. + --this.pos; + this.state = ParserState.BeforeAttributeName; + break; + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#character-reference-state + characterReference() { + this.temporaryBuffer = [Chars.AMPERSAND]; + const next = this.consume(); + if (isalnum(next)) { + --this.pos; + this.namedCharacterReference(); + } else if (next == Chars.NUMBER_SIGN) { + this.temporaryBuffer.push(next); + this.numericCharacterReference(); + } else { + --this.pos; + this.flushTemporaryBuffer(); + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state + // Intentionally only handling part of an attribute + namedCharacterReference() { + // Consume the maximum number of characters possible, where the consumed + // characters are one of the identifiers in the first column of the named + // character references table. Append each character to the temporary buffer + // when it's consumed. + let currentTrie = htmlEntitiesTrie; + for (;;) { + const ch = this.consume(); + this.temporaryBuffer.push(ch); + + if (currentTrie.children && ch in currentTrie.children) { + currentTrie = currentTrie.children[ch]!; + } else { + --this.pos; + this.temporaryBuffer.pop(); + const lastChar = + this.temporaryBuffer[this.temporaryBuffer.length - 1]; + + // If there is a match + if (currentTrie.data) { + // If the character reference was consumed as part of an attribute, + // and the last character matched is not a U+003B SEMICOLON character (;), + // and the next input character is either a U+003D EQUALS SIGN character (=) + // or an ASCII alphanumeric, then, for historical reasons, flush code points + // consumed as a character reference and switch to the return state. + if ( + lastChar != Chars.SEMICOLON && + (this.peek() == Chars.EQUALS || isalpha(this.peek())) + ) { + this.flushTemporaryBuffer(); + return; + } else { + // missing-semicolon-after-character-reference parse error + this.temporaryBuffer = currentTrie.data.p; + this.flushTemporaryBuffer(); + return; + } + } else { + this.flushTemporaryBuffer(); + this.ambiguousAmpersand(); + return; + } + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#ambiguous-ampersand-state + ambiguousAmpersand() { + const ch = this.consume(); + if (isalnum(ch)) { + this.currentAttributeValue += String.fromCharCode(ch); + } else { + --this.pos; + return; + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-state + numericCharacterReference() { + this.characterReferenceCode = 0; + const ch = this.consume(); + switch (ch) { + case Chars.LOWERCASE_X: + case Chars.UPPERCASE_X: + this.temporaryBuffer.push(ch); + this.hexadecimalCharacterReferenceStart(); + break; + default: + --this.pos; + this.decimalCharacterReferenceStart(); + break; + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#hexadecimal-character-reference-start-state + hexadecimalCharacterReferenceStart() { + const ch = this.consume(); + + if (isxdigit(ch)) { + --this.pos; + this.hexadecimalCharacterReference(); + } else { + --this.pos; + this.flushTemporaryBuffer(); + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#decimal-character-reference-start-state + decimalCharacterReferenceStart() { + const ch = this.consume(); + if (isdigit(ch)) { + --this.pos; + this.decimalCharacterReference(); + } else { + --this.pos; + this.flushTemporaryBuffer(); + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#hexadecimal-character-reference-state + hexadecimalCharacterReference() { + for (;;) { + const ch = this.consume(); + if (isdigit(ch)) { + this.characterReferenceCode *= 16; + this.characterReferenceCode += ch - 0x30; + } else if (Chars.UPPERCASE_A <= ch && ch <= Chars.UPPERCASE_F) { + this.characterReferenceCode *= 16; + this.characterReferenceCode += ch - 0x37; + } else if (Chars.LOWERCASE_A <= ch && ch <= Chars.LOWERCASE_F) { + this.characterReferenceCode *= 16; + this.characterReferenceCode += ch - 0x57; + } else if (ch === Chars.SEMICOLON) { + this.numericCharacterReferenceEndState(); + return; + } else { + --this.pos; + this.numericCharacterReferenceEndState(); + return; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#decimal-character-reference-state + decimalCharacterReference() { + for (;;) { + const ch = this.consume(); + if (isdigit(ch)) { + this.characterReferenceCode *= 10; + this.characterReferenceCode += ch - 0x30; + } else if (ch === Chars.SEMICOLON) { + this.numericCharacterReferenceEndState(); + return; + } else { + --this.pos; + this.numericCharacterReferenceEndState(); + return; + } + } + } + + // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state + numericCharacterReferenceEndState() { + if (this.characterReferenceCode == 0) { + // null-character-reference parse error + this.characterReferenceCode = 0xfffd; + } + + if (this.characterReferenceCode > 0x10ffff) { + // character-reference-outside-unicode-range parse error + this.characterReferenceCode = 0xfffd; + } + + if (isSurrogate(this.characterReferenceCode)) { + // surrogate-character-reference parse error + this.characterReferenceCode = 0xfffd; + } + + // If the number is a noncharacter, then this is a noncharacter-character-reference parse error. + // ... and do nothing, so don't bother checking. + + // Handle replacements + this.characterReferenceCode = + characterReferenceCodePointReplacements.get( + this.characterReferenceCode, + ) ?? this.characterReferenceCode; + + this.temporaryBuffer = [this.characterReferenceCode]; + this.flushTemporaryBuffer(); + } + + private flushTemporaryBuffer() { + this.currentAttributeValue += String.fromCodePoint( + ...this.temporaryBuffer, + ); + this.temporaryBuffer = []; + } +} + +// https://infra.spec.whatwg.org/#leading-surrogate +function isLeadingSurrogate(ch: number) { + return 0xd800 <= ch && ch <= 0xdbff; +} + +// https://infra.spec.whatwg.org/#trailing-surrogate +function isTrailingSurrogate(ch: number) { + return 0xdc00 <= ch && ch <= 0xdfff; +} + +// https://infra.spec.whatwg.org/#surrogate +function isSurrogate(ch: number) { + return isLeadingSurrogate(ch) || isTrailingSurrogate(ch); +} + +const characterReferenceCodePointReplacements = new Map([ + [0x80, 0x20ac], // EURO SIGN (€) + [0x82, 0x201a], // SINGLE LOW-9 QUOTATION MARK (‚) + [0x83, 0x0192], // LATIN SMALL LETTER F WITH HOOK (ƒ) + [0x84, 0x201e], // DOUBLE LOW-9 QUOTATION MARK („) + [0x85, 0x2026], // HORIZONTAL ELLIPSIS (…) + [0x86, 0x2020], // DAGGER (†) + [0x87, 0x2021], // DOUBLE DAGGER (‡) + [0x88, 0x02c6], // MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ) + [0x89, 0x2030], // PER MILLE SIGN (‰) + [0x8a, 0x0160], // LATIN CAPITAL LETTER S WITH CARON (Š) + [0x8b, 0x2039], // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹) + [0x8c, 0x0152], // LATIN CAPITAL LIGATURE OE (Œ) + [0x8e, 0x017d], // LATIN CAPITAL LETTER Z WITH CARON (Ž) + [0x91, 0x2018], // LEFT SINGLE QUOTATION MARK (‘) + [0x92, 0x2019], // RIGHT SINGLE QUOTATION MARK (’) + [0x93, 0x201c], // LEFT DOUBLE QUOTATION MARK (“) + [0x94, 0x201d], // RIGHT DOUBLE QUOTATION MARK (”) + [0x95, 0x2022], // BULLET (•) + [0x96, 0x2013], // EN DASH (–) + [0x97, 0x2014], // EM DASH (—) + [0x98, 0x02dc], // SMALL TILDE (˜) + [0x99, 0x2122], // TRADE MARK SIGN (™) + [0x9a, 0x0161], // LATIN SMALL LETTER S WITH CARON (š) + [0x9b, 0x203a], // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›) + [0x9c, 0x0153], // LATIN SMALL LIGATURE OE (œ) + [0x9e, 0x017e], // LATIN SMALL LETTER Z WITH CARON (ž) + [0x9f, 0x0178], // LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ) +]); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 650c89df2..318b68f7c 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -8,7 +8,7 @@ export { } from "./array"; export { AbstractComponent, ChildableComponent, Component } from "./component"; export * from "./enum"; -export { Event, EventDispatcher } from "./events"; +export { EventDispatcher } from "./events"; export { isFile, copy, @@ -28,7 +28,6 @@ export { DefaultMap } from "./map"; export { ArgumentsReader, Option, - BindOption, CommentStyle, Options, PackageJsonReader, diff --git a/src/lib/utils/jsx.elements.ts b/src/lib/utils/jsx.elements.ts index f858080e7..7893b1bc2 100644 --- a/src/lib/utils/jsx.elements.ts +++ b/src/lib/utils/jsx.elements.ts @@ -122,6 +122,7 @@ export interface IntrinsicElements { ellipse: JsxEllipseElementProps; polygon: JsxPolygonElementProps; polyline: JsxPolylineElementProps; + line: JsxLineElementProps; use: JsxUseElementProps; } @@ -591,7 +592,7 @@ export interface JsxMetaElementProps extends JsxHtmlGlobalProps { | "default-style" | "x-ua-compatible" | "refresh"; - charSet?: "utf-8"; + charset?: "utf-8"; content?: string; name?: string; } @@ -1136,6 +1137,22 @@ export interface JsxPolylineElementProps pathLength?: number; } +/** Properties permitted on the `<line>` element. + * + * Reference: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line + */ +export interface JsxLineElementProps + extends JsxSvgCoreProps, + JsxSvgStyleProps, + JsxSvgConditionalProcessingProps, + JsxSvgPresentationProps { + x1?: string | number; + y1?: string | number; + x2?: string | number; + y2?: string | number; + pathLength?: number; +} + /** * Properties permitted on the `<use>` element. * diff --git a/src/lib/utils/loggers.ts b/src/lib/utils/loggers.ts index 9a6cabfea..8ee2bc812 100644 --- a/src/lib/utils/loggers.ts +++ b/src/lib/utils/loggers.ts @@ -3,6 +3,11 @@ import { url } from "inspector"; import { resolve } from "path"; import { nicePath } from "./paths"; import type { MinimalSourceFile } from "./minimalSourceFile"; +import type { + TranslatedString, + TranslationProxy, +} from "../internationalization/internationalization"; +import type { IfInternal } from "."; const isDebugging = () => !!url(); @@ -27,7 +32,7 @@ const Colors = { }; function color(text: string, color: keyof typeof Colors) { - if ("NO_COLOR" in process.env) return `${text}`; + if ("NO_COLOR" in process.env) return text; return `${Colors[color]}${text}${Colors.reset}`; } @@ -39,6 +44,18 @@ const messagePrefixes = { [LogLevel.Verbose]: color("[debug]", "gray"), }; +const dummyTranslationProxy: TranslationProxy = new Proxy( + {} as TranslationProxy, + { + get: (_target, key) => { + return (...args: string[]) => + String(key).replace(/\{(\d+)\}/g, (_, index) => { + return args[+index] ?? "(no placeholder)"; + }); + }, + }, +); + type FormatArgs = [ts.Node?] | [number, MinimalSourceFile]; /** @@ -48,6 +65,13 @@ type FormatArgs = [ts.Node?] | [number, MinimalSourceFile]; * all the required utility functions. */ export class Logger { + /** + * Translation utility for internationalization. + * @privateRemarks + * This is fully initialized by the application during bootstrapping. + */ + i18n: TranslationProxy = dummyTranslationProxy; + /** * How many error messages have been logged? */ @@ -106,7 +130,7 @@ export class Logger { } /** Log the given info message. */ - info(text: string) { + info(text: IfInternal<TranslatedString, string>) { this.log(this.addContext(text, LogLevel.Info), LogLevel.Info); } @@ -115,8 +139,12 @@ export class Logger { * * @param text The warning that should be logged. */ - warn(text: string, node?: ts.Node): void; - warn(text: string, pos: number, file: MinimalSourceFile): void; + warn(text: IfInternal<TranslatedString, string>, node?: ts.Node): void; + warn( + text: IfInternal<TranslatedString, string>, + pos: number, + file: MinimalSourceFile, + ): void; warn(text: string, ...args: FormatArgs): void { const text2 = this.addContext(text, LogLevel.Warn, ...args); if (this.seenWarnings.has(text2) && !isDebugging()) return; @@ -129,8 +157,12 @@ export class Logger { * * @param text The error that should be logged. */ - error(text: string, node?: ts.Node): void; - error(text: string, pos: number, file: MinimalSourceFile): void; + error(text: IfInternal<TranslatedString, string>, node?: ts.Node): void; + error( + text: IfInternal<TranslatedString, string>, + pos: number, + file: MinimalSourceFile, + ): void; error(text: string, ...args: FormatArgs) { const text2 = this.addContext(text, LogLevel.Error, ...args); if (this.seenErrors.has(text2) && !isDebugging()) return; @@ -138,17 +170,6 @@ export class Logger { this.log(text2, LogLevel.Error); } - /** @internal */ - deprecated(text: string, addStack = true) { - if (addStack) { - const stack = new Error().stack?.split("\n"); - if (stack && stack.length >= 4) { - text = text + "\n" + stack[3]; - } - } - this.warn(text); - } - /** * Print a log message. * @@ -212,13 +233,6 @@ export class Logger { * A logger that outputs all messages to the console. */ export class ConsoleLogger extends Logger { - /** - * Create a new ConsoleLogger instance. - */ - constructor() { - super(); - } - /** * Print a log message. * diff --git a/src/lib/utils/map.ts b/src/lib/utils/map.ts index aa6b22475..bc7a8b1de 100644 --- a/src/lib/utils/map.ts +++ b/src/lib/utils/map.ts @@ -1,5 +1,5 @@ export class DefaultMap<K, V> extends Map<K, V> { - constructor(private creator: () => V) { + constructor(private creator: (key: K) => V) { super(); } @@ -9,7 +9,7 @@ export class DefaultMap<K, V> extends Map<K, V> { return saved; } - const created = this.creator(); + const created = this.creator(key); this.set(key, created); return created; } diff --git a/src/lib/utils/minimalSourceFile.ts b/src/lib/utils/minimalSourceFile.ts index 18d821f19..f597b96b8 100644 --- a/src/lib/utils/minimalSourceFile.ts +++ b/src/lib/utils/minimalSourceFile.ts @@ -23,7 +23,7 @@ export class MinimalSourceFile implements SourceFileLike { while (pos >= starts[starts.length - 1]) { const nextStart = this.text.indexOf( "\n", - starts[starts.length - 1] + 1, + starts[starts.length - 1], ); if (nextStart === -1) { diff --git a/src/lib/utils/options/declaration.ts b/src/lib/utils/options/declaration.ts index 2a9b4861b..1b967b543 100644 --- a/src/lib/utils/options/declaration.ts +++ b/src/lib/utils/options/declaration.ts @@ -1,9 +1,14 @@ -import type { Theme as ShikiTheme } from "shiki"; +import type { BundledTheme as ShikiTheme } from "shiki" with { "resolution-mode": "import" }; import type { LogLevel } from "../loggers"; import type { SortStrategy } from "../sort"; import { isAbsolute, join, resolve } from "path"; import type { EntryPointStrategy } from "../entry-point"; import type { ReflectionKind } from "../../models/reflections/kind"; +import type { NeverIfInternal } from ".."; +import type { + TranslatedString, + TranslationProxy, +} from "../../internationalization/internationalization"; /** @enum */ export const EmitStrategy = { @@ -36,16 +41,16 @@ export type TypeDocOptions = { [K in keyof TypeDocOptionMap]: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption< - infer ManuallyValidated - > - ? ManuallyValidated - : TypeDocOptionMap[K] extends string | string[] | number | boolean - ? TypeDocOptionMap[K] - : TypeDocOptionMap[K] extends Record<string, boolean> - ? Partial<TypeDocOptionMap[K]> | boolean - : - | keyof TypeDocOptionMap[K] - | TypeDocOptionMap[K][keyof TypeDocOptionMap[K]]; + infer ManuallyValidated + > + ? ManuallyValidated + : TypeDocOptionMap[K] extends string | string[] | number | boolean + ? TypeDocOptionMap[K] + : TypeDocOptionMap[K] extends Record<string, boolean> + ? Partial<TypeDocOptionMap[K]> | boolean + : + | keyof TypeDocOptionMap[K] + | TypeDocOptionMap[K][keyof TypeDocOptionMap[K]]; }; /** @@ -58,17 +63,17 @@ export type TypeDocOptionValues = { [K in keyof TypeDocOptionMap]: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption< - infer ManuallyValidated - > - ? ManuallyValidated - : TypeDocOptionMap[K] extends - | string - | string[] - | number - | boolean - | Record<string, boolean> - ? TypeDocOptionMap[K] - : TypeDocOptionMap[K][keyof TypeDocOptionMap[K]]; + infer ManuallyValidated + > + ? ManuallyValidated + : TypeDocOptionMap[K] extends + | string + | string[] + | number + | boolean + | Record<string, boolean> + ? TypeDocOptionMap[K] + : TypeDocOptionMap[K][keyof TypeDocOptionMap[K]]; }; /** @@ -92,15 +97,20 @@ export interface TypeDocOptionMap { tsconfig: string; compilerOptions: unknown; plugin: string[]; + lang: string; + locales: ManuallyValidatedOption<Record<string, Record<string, string>>>; + packageOptions: ManuallyValidatedOption<TypeDocOptions>; // Input entryPoints: string[]; entryPointStrategy: typeof EntryPointStrategy; + alwaysCreateEntryPointModule: boolean; + projectDocuments: string[]; exclude: string[]; externalPattern: string[]; excludeExternals: boolean; excludeNotDocumented: boolean; - excludeNotDocumentedKinds: Array<keyof typeof ReflectionKind>; + excludeNotDocumentedKinds: ReflectionKind.KindString[]; excludeInternal: boolean; excludePrivate: boolean; excludeProtected: boolean; @@ -116,7 +126,6 @@ export interface TypeDocOptionMap { gitRevision: string; gitRemote: string; readme: string; - stripYamlFrontmatter: boolean; // Output out: string; @@ -126,18 +135,42 @@ export interface TypeDocOptionMap { theme: string; lightHighlightTheme: ShikiTheme; darkHighlightTheme: ShikiTheme; + highlightLanguages: string[]; customCss: string; - markedOptions: unknown; + markdownItOptions: ManuallyValidatedOption<Record<string, unknown>>; + /** + * Will be called when TypeDoc is setting up the markdown parser to use to render markdown. + * Can be used to add markdown-it plugins to the parser with code like this: + * + * ```ts + * // typedoc.config.mjs + * import iterator from "markdown-it-for-inline"; + * export default { + * /** @param {MarkdownIt} parser *\/ + * markdownItLoader(parser) { + * parser.use(iterator, "foo_replace", "text", function(tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * } + * } + * ``` + * + * Note: Unfortunately, markdown-it doesn't ship its own types, so `parser` isn't + * strictly typed here. + */ + markdownItLoader: ManuallyValidatedOption<(parser: any) => void>; basePath: string; cname: string; - htmlLang: string; githubPages: boolean; - sitemapBaseUrl: string; + hostedBaseUrl: string; + useHostedBaseUrlForAbsoluteLinks: boolean; cacheBust: boolean; - gaID: string; hideGenerator: boolean; + customFooterHtml: string; + customFooterHtmlDisableWrapper: boolean; hideParameterTypesInTitle: boolean; searchInComments: boolean; + searchInDocuments: boolean; cleanOutputDir: boolean; titleLink: string; navigationLinks: ManuallyValidatedOption<Record<string, string>>; @@ -147,7 +180,6 @@ export interface TypeDocOptionMap { includeCategories: boolean; includeGroups: boolean; includeFolders: boolean; - fullTree: boolean; }; visibilityFilters: ManuallyValidatedOption<{ protected?: boolean; @@ -164,6 +196,7 @@ export interface TypeDocOptionMap { useTsLinkResolution: boolean; preserveLinkText: boolean; jsDocCompatibility: JsDocCompatibility; + suppressCommentWarningsInDeclarationFiles: boolean; blockTags: `@${string}`[]; inlineTags: `@${string}`[]; modifierTags: `@${string}`[]; @@ -171,8 +204,7 @@ export interface TypeDocOptionMap { externalSymbolLinkMappings: ManuallyValidatedOption< Record<string, Record<string, string>> >; - media: string; - includes: string; + cascadedModifierTags: `@${string}`[]; // Organization categorizeByGroup: boolean; @@ -252,26 +284,35 @@ export type KeyToDeclaration<K extends keyof TypeDocOptionMap> = TypeDocOptionMap[K] extends boolean ? BooleanDeclarationOption : TypeDocOptionMap[K] extends string - ? StringDeclarationOption - : TypeDocOptionMap[K] extends number - ? NumberDeclarationOption - : TypeDocOptionMap[K] extends string[] - ? ArrayDeclarationOption - : unknown extends TypeDocOptionMap[K] - ? MixedDeclarationOption | ObjectDeclarationOption - : TypeDocOptionMap[K] extends ManuallyValidatedOption<unknown> - ? - | (MixedDeclarationOption & { - validate(value: unknown): void; - }) - | (ObjectDeclarationOption & { - validate(value: unknown): void; - }) - : TypeDocOptionMap[K] extends Record<string, boolean> - ? FlagsDeclarationOption<TypeDocOptionMap[K]> - : TypeDocOptionMap[K] extends Record<string | number, infer U> - ? MapDeclarationOption<U> - : never; + ? StringDeclarationOption + : TypeDocOptionMap[K] extends number + ? NumberDeclarationOption + : TypeDocOptionMap[K] extends string[] + ? ArrayDeclarationOption + : unknown extends TypeDocOptionMap[K] + ? MixedDeclarationOption | ObjectDeclarationOption + : TypeDocOptionMap[K] extends ManuallyValidatedOption<unknown> + ? + | (MixedDeclarationOption & { + validate( + value: unknown, + i18n: TranslationProxy, + ): void; + }) + | (ObjectDeclarationOption & { + validate( + value: unknown, + i18n: TranslationProxy, + ): void; + }) + : TypeDocOptionMap[K] extends Record<string, boolean> + ? FlagsDeclarationOption<TypeDocOptionMap[K]> + : TypeDocOptionMap[K] extends Record< + string | number, + infer U + > + ? MapDeclarationOption<U> + : never; export enum ParameterHint { File, @@ -319,8 +360,12 @@ export interface DeclarationOptionBase { /** * The help text to be displayed to the user when --help is passed. + * + * This may be a string, which will be presented directly, or a function, + * which will be called with an {@link TranslationProxy} so that option help + * can be translated into the user specified locale. */ - help: string; + help: NeverIfInternal<string> | ((i18n: TranslationProxy) => string); /** * The parameter type, used to convert user configuration values into the expected type. @@ -356,7 +401,7 @@ export interface StringDeclarationOption extends DeclarationOptionBase { * An optional validation function that validates a potential value of this option. * The function must throw an Error if the validation fails and should do nothing otherwise. */ - validate?: (value: string) => void; + validate?: (value: string, i18n: TranslationProxy) => void; } export interface NumberDeclarationOption extends DeclarationOptionBase { @@ -381,7 +426,7 @@ export interface NumberDeclarationOption extends DeclarationOptionBase { * An optional validation function that validates a potential value of this option. * The function must throw an Error if the validation fails and should do nothing otherwise. */ - validate?: (value: number) => void; + validate?: (value: number, i18n: TranslationProxy) => void; } export interface BooleanDeclarationOption extends DeclarationOptionBase { @@ -409,7 +454,7 @@ export interface ArrayDeclarationOption extends DeclarationOptionBase { * An optional validation function that validates a potential value of this option. * The function must throw an Error if the validation fails and should do nothing otherwise. */ - validate?: (value: string[]) => void; + validate?: (value: string[], i18n: TranslationProxy) => void; } export interface MixedDeclarationOption extends DeclarationOptionBase { @@ -424,7 +469,7 @@ export interface MixedDeclarationOption extends DeclarationOptionBase { * An optional validation function that validates a potential value of this option. * The function must throw an Error if the validation fails and should do nothing otherwise. */ - validate?: (value: unknown) => void; + validate?: (value: unknown, i18n: TranslationProxy) => void; } export interface ObjectDeclarationOption extends DeclarationOptionBase { @@ -439,7 +484,7 @@ export interface ObjectDeclarationOption extends DeclarationOptionBase { * An optional validation function that validates a potential value of this option. * The function must throw an Error if the validation fails and should do nothing otherwise. */ - validate?: (value: unknown) => void; + validate?: (value: unknown, i18n: TranslationProxy) => void; } export interface MapDeclarationOption<T> extends DeclarationOptionBase { type: ParameterType.Map; @@ -456,11 +501,6 @@ export interface MapDeclarationOption<T> extends DeclarationOptionBase { * The default value for a mapped type must be specified. */ defaultValue: T; - - /** - * Optional override for the error reported when an invalid key is provided. - */ - mapError?: string; } export interface FlagsDeclarationOption<T extends Record<string, boolean>> @@ -504,52 +544,58 @@ export type DeclarationOptionToOptionType<T extends DeclarationOption> = T extends MapDeclarationOption<infer U> ? U : T extends FlagsDeclarationOption<infer U> - ? U - : ParameterTypeToOptionTypeMap[Exclude<T["type"], undefined>]; + ? U + : ParameterTypeToOptionTypeMap[Exclude<T["type"], undefined>]; const converters: { [K in ParameterType]: ( value: unknown, option: DeclarationOption & { type: K }, + i18n: TranslationProxy, configPath: string, oldValue: unknown, ) => ParameterTypeToOptionTypeMap[K]; } = { - [ParameterType.String](value, option) { + [ParameterType.String](value, option, i18n) { const stringValue = value == null ? "" : String(value); - option.validate?.(stringValue); + option.validate?.(stringValue, i18n); return stringValue; }, - [ParameterType.Path](value, option, configPath) { + [ParameterType.Path](value, option, i18n, configPath) { const stringValue = value == null ? "" : resolve(configPath, String(value)); - option.validate?.(stringValue); + option.validate?.(stringValue, i18n); return stringValue; }, - [ParameterType.Number](value, option) { + [ParameterType.Number](value, option, i18n) { const numValue = parseInt(String(value), 10) || 0; if (!valueIsWithinBounds(numValue, option.minValue, option.maxValue)) { throw new Error( - getBoundsError(option.name, option.minValue, option.maxValue), + getBoundsError( + option.name, + i18n, + option.minValue, + option.maxValue, + ), ); } - option.validate?.(numValue); + option.validate?.(numValue, i18n); return numValue; }, [ParameterType.Boolean](value) { return !!value; }, - [ParameterType.Array](value, option) { + [ParameterType.Array](value, option, i18n) { let strArrValue = new Array<string>(); if (Array.isArray(value)) { strArrValue = value.map(String); } else if (typeof value === "string") { strArrValue = [value]; } - option.validate?.(strArrValue); + option.validate?.(strArrValue, i18n); return strArrValue; }, - [ParameterType.PathArray](value, option, configPath) { + [ParameterType.PathArray](value, option, i18n, configPath) { let strArrValue = new Array<string>(); if (Array.isArray(value)) { strArrValue = value.map(String); @@ -557,10 +603,10 @@ const converters: { strArrValue = [value]; } strArrValue = strArrValue.map((path) => resolve(configPath, path)); - option.validate?.(strArrValue); + option.validate?.(strArrValue, i18n); return strArrValue; }, - [ParameterType.ModuleArray](value, option, configPath) { + [ParameterType.ModuleArray](value, option, i18n, configPath) { let strArrValue = new Array<string>(); if (Array.isArray(value)) { strArrValue = value.map(String); @@ -568,10 +614,10 @@ const converters: { strArrValue = [value]; } strArrValue = resolveModulePaths(strArrValue, configPath); - option.validate?.(strArrValue); + option.validate?.(strArrValue, i18n); return strArrValue; }, - [ParameterType.GlobArray](value, option, configPath) { + [ParameterType.GlobArray](value, option, i18n, configPath) { let strArrValue = new Array<string>(); if (Array.isArray(value)) { strArrValue = value.map(String); @@ -579,10 +625,10 @@ const converters: { strArrValue = [value]; } strArrValue = resolveGlobPaths(strArrValue, configPath); - option.validate?.(strArrValue); + option.validate?.(strArrValue, i18n); return strArrValue; }, - [ParameterType.Map](value, option) { + [ParameterType.Map](value, option, i18n) { const key = String(value); if (option.map instanceof Map) { if (option.map.has(key)) { @@ -598,21 +644,19 @@ const converters: { } else if (Object.values(option.map).includes(value)) { return value; } - throw new Error( - option.mapError ?? getMapError(option.map, option.name), - ); + throw new Error(getMapError(option.map, i18n, option.name)); }, - [ParameterType.Mixed](value, option) { - option.validate?.(value); + [ParameterType.Mixed](value, option, i18n) { + option.validate?.(value, i18n); return value; }, - [ParameterType.Object](value, option, _configPath, oldValue) { - option.validate?.(value); + [ParameterType.Object](value, option, i18n, _configPath, oldValue) { + option.validate?.(value, i18n); if (typeof oldValue !== "undefined") value = { ...(oldValue as {}), ...(value as {}) }; return value; }, - [ParameterType.Flags](value, option) { + [ParameterType.Flags](value, option, i18n) { if (typeof value === "boolean") { value = Object.fromEntries( Object.keys(option.defaults).map((key) => [key, value]), @@ -621,7 +665,7 @@ const converters: { if (typeof value !== "object" || value == null) { throw new Error( - `Expected an object with flag values for ${option.name} or true/false`, + i18n.expected_object_with_flag_values_for_0(option.name), ); } const obj = { ...value } as Record<string, unknown>; @@ -629,11 +673,11 @@ const converters: { for (const key of Object.keys(obj)) { if (!Object.prototype.hasOwnProperty.call(option.defaults, key)) { throw new Error( - `The flag '${key}' is not valid for ${ - option.name - }, expected one of: ${Object.keys(option.defaults).join( - ", ", - )}`, + i18n.flag_0_is_not_valid_for_1_expected_2( + key, + option.name, + Object.keys(option.defaults).join(", "), + ), ); } @@ -643,7 +687,7 @@ const converters: { obj[key] = option.defaults[key]; } else { throw new Error( - `Flag values for ${option.name} must be a boolean.`, + i18n.flag_values_for_0_must_be_booleans(option.name), ); } } @@ -663,16 +707,24 @@ const converters: { export function convert( value: unknown, option: DeclarationOption, + i18n: TranslationProxy, configPath: string, oldValue?: unknown, ): unknown { const _converters = converters as Record< ParameterType, - (v: unknown, o: DeclarationOption, c: string, ov: unknown) => unknown + ( + v: unknown, + o: DeclarationOption, + i18n: TranslationProxy, + c: string, + ov: unknown, + ) => unknown >; return _converters[option.type ?? ParameterType.String]( value, option, + i18n, configPath, oldValue, ); @@ -775,8 +827,9 @@ function isTsNumericEnum(map: Record<string, any>) { */ function getMapError( map: MapDeclarationOption<unknown>["map"], + i18n: TranslationProxy, name: string, -): string { +): TranslatedString { let keys = map instanceof Map ? [...map.keys()] : Object.keys(map); // If the map is a TS numeric enum we need to filter out the numeric keys. @@ -786,7 +839,7 @@ function getMapError( keys = keys.filter((key) => Number.isNaN(parseInt(key, 10))); } - return `${name} must be one of ${keys.join(", ")}`; + return i18n.option_0_must_be_one_of_1(name, keys.join(", ")); } /** @@ -798,15 +851,26 @@ function getMapError( */ function getBoundsError( name: string, + i18n: TranslationProxy, minValue?: number, maxValue?: number, -): string { +): TranslatedString { if (isFiniteNumber(minValue) && isFiniteNumber(maxValue)) { - return `${name} must be between ${minValue} and ${maxValue}`; + return i18n.option_0_must_be_between_1_and_2( + name, + String(minValue), + String(maxValue), + ); } else if (isFiniteNumber(minValue)) { - return `${name} must be >= ${minValue}`; + return i18n.option_0_must_be_equal_to_or_greater_than_1( + name, + String(minValue), + ); } else { - return `${name} must be <= ${maxValue}`; + return i18n.option_0_must_be_less_than_or_equal_to_1( + name, + String(maxValue), + ); } } diff --git a/src/lib/utils/options/help.ts b/src/lib/utils/options/help.ts index 85eba1042..79d0849c8 100644 --- a/src/lib/utils/options/help.ts +++ b/src/lib/utils/options/help.ts @@ -1,12 +1,15 @@ import type { Options } from "./options"; import { ParameterHint, - StringDeclarationOption, + type StringDeclarationOption, ParameterType, - DeclarationOption, + type DeclarationOption, } from "./declaration"; -import { getSupportedLanguages } from "../highlighter"; -import { BUNDLED_THEMES } from "shiki"; +import { + getSupportedLanguagesWithoutAliases, + getSupportedThemes, +} from "../highlighter"; +import type { TranslationProxy } from "../../internationalization/internationalization"; export interface ParameterHelp { names: string[]; @@ -29,7 +32,10 @@ function hasHint( * @param scope The scope of the parameters whose help should be returned. * @returns The columns and lines for the help of the requested parameters. */ -function getParameterHelp(options: Options): ParameterHelp { +function getParameterHelp( + options: Options, + i18n: TranslationProxy, +): ParameterHelp { const parameters = options.getDeclarations(); parameters.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }), @@ -50,7 +56,11 @@ function getParameterHelp(options: Options): ParameterHelp { } names.push(name); - helps.push(parameter.help); + helps.push( + typeof parameter.help === "string" + ? parameter.help + : parameter.help(i18n), + ); margin = Math.max(name.length, margin); } @@ -61,7 +71,7 @@ function toEvenColumns(values: string[], maxLineWidth: number) { const columnWidth = values.reduce((acc, val) => Math.max(acc, val.length), 0) + 2; - const numColumns = Math.max(1, Math.min(maxLineWidth / columnWidth)); + const numColumns = Math.max(1, Math.floor(maxLineWidth / columnWidth)); let line = ""; const out: string[] = []; @@ -79,10 +89,13 @@ function toEvenColumns(values: string[], maxLineWidth: number) { return out; } -export function getOptionsHelp(options: Options): string { - const output = ["Usage:", " typedoc path/to/entry.ts", "", "Options:"]; +export function getOptionsHelp( + options: Options, + i18n: TranslationProxy, +): string { + const output = ["typedoc path/to/entry.ts", "", "Options:"]; - const columns = getParameterHelp(options); + const columns = getParameterHelp(options, i18n); for (let i = 0; i < columns.names.length; i++) { const usage = columns.names[i]; const description = columns.helps[i]; @@ -92,13 +105,13 @@ export function getOptionsHelp(options: Options): string { output.push( "", "Supported highlighting languages:", - ...toEvenColumns(getSupportedLanguages(), 80), + ...toEvenColumns(getSupportedLanguagesWithoutAliases(), 80), ); output.push( "", "Supported highlighting themes:", - ...toEvenColumns(BUNDLED_THEMES, 80), + ...toEvenColumns(getSupportedThemes(), 80), ); return output.join("\n"); diff --git a/src/lib/utils/options/index.ts b/src/lib/utils/options/index.ts index 770fa44a6..8f5308f5d 100644 --- a/src/lib/utils/options/index.ts +++ b/src/lib/utils/options/index.ts @@ -1,4 +1,4 @@ -export { Options, Option, BindOption } from "./options"; +export { Options, Option } from "./options"; export type { OptionsReader } from "./options"; export { ArgumentsReader, diff --git a/src/lib/utils/options/options.ts b/src/lib/utils/options/options.ts index 212bd86dd..e186f1d09 100644 --- a/src/lib/utils/options/options.ts +++ b/src/lib/utils/options/options.ts @@ -1,20 +1,22 @@ import type * as ts from "typescript"; import { ParameterType } from "./declaration"; import type { NeverIfInternal } from ".."; +import { DefaultMap } from "../map"; import type { Application } from "../../.."; import { insertOrderSorted, unique } from "../array"; import type { Logger } from "../loggers"; import { convert, - DeclarationOption, + type DeclarationOption, getDefaultValue, - KeyToDeclaration, - TypeDocOptionMap, - TypeDocOptions, - TypeDocOptionValues, + type KeyToDeclaration, + type TypeDocOptionMap, + type TypeDocOptions, + type TypeDocOptionValues, } from "./declaration"; import { addTypeDocOptions } from "./sources"; import { getOptionsHelp } from "./help"; +import type { TranslationProxy } from "../../internationalization/internationalization"; /** * Describes an option reader that discovers user configuration and converts it to the @@ -60,7 +62,7 @@ export interface OptionsReader { const optionSnapshots = new WeakMap< { __optionSnapshot: never }, { - values: string; + values: Record<string, unknown>; set: Set<string>; } >(); @@ -93,13 +95,15 @@ export class Options { private _compilerOptions: ts.CompilerOptions = {}; private _fileNames: readonly string[] = []; private _projectReferences: readonly ts.ProjectReference[] = []; + private _i18n: TranslationProxy; /** * In packages mode, the directory of the package being converted. */ packageDir?: string; - constructor() { + constructor(i18n: TranslationProxy) { + this._i18n = i18n; addTypeDocOptions(this); } @@ -107,7 +111,7 @@ export class Options { * Clones the options, intended for use in packages mode. */ copyForPackage(packageDir: string): Options { - const options = new Options(); + const options = new Options(this._i18n); options.packageDir = packageDir; options._readers = this._readers.filter( @@ -116,6 +120,12 @@ export class Options { options._declarations = new Map(this._declarations); options.reset(); + for (const [key, val] of Object.entries( + this.getValue("packageOptions"), + )) { + options.setValue(key as any, val, packageDir); + } + return options; } @@ -141,7 +151,7 @@ export class Options { const key = {} as { __optionSnapshot: never }; optionSnapshots.set(key, { - values: JSON.stringify(this._values), + values: { ...this._values }, set: new Set(this._setOptions), }); @@ -154,7 +164,7 @@ export class Options { */ restore(snapshot: { __optionSnapshot: never }) { const data = optionSnapshots.get(snapshot)!; - this._values = JSON.parse(data.values); + this._values = { ...data.values }; this._setOptions = new Set(data.set); } @@ -277,9 +287,10 @@ export class Options { if (!declaration) { const nearNames = this.getSimilarOptions(name); throw new Error( - `Unknown option '${name}', you may have meant:\n\t${nearNames.join( - "\n\t", - )}`, + this._i18n.unknown_option_0_you_may_have_meant_1( + name, + nearNames.join("\n\t"), + ), ); } @@ -313,9 +324,10 @@ export class Options { if (!declaration) { const nearNames = this.getSimilarOptions(name); throw new Error( - `Tried to set an option (${name}) that was not declared. You may have meant:\n\t${nearNames.join( - "\n\t", - )}`, + this._i18n.unknown_option_0_you_may_have_meant_1( + name, + nearNames.join("\n\t"), + ), ); } @@ -326,12 +338,17 @@ export class Options { const converted = convert( value, declaration, + this._i18n, configPath ?? process.cwd(), oldValue, ); if (declaration.type === ParameterType.Flags) { - Object.assign(this._values[declaration.name] as any, converted); + this._values[declaration.name] = Object.assign( + {}, + this._values[declaration.name], + converted, + ); } else { this._values[declaration.name] = converted; } @@ -409,28 +426,26 @@ export class Options { * Discover similar option names to the given name, for use in error reporting. */ getSimilarOptions(missingName: string): string[] { - const results: Record<number, string[]> = {}; + const results = new DefaultMap<number, string[]>(() => []); let lowest = Infinity; for (const name of this._declarations.keys()) { const distance = editDistance(missingName, name); lowest = Math.min(lowest, distance); - results[distance] ||= []; - results[distance].push(name); + results.get(distance).push(name); } // Experimenting a bit, it seems an edit distance of 3 is roughly the // right metric for relevant "similar" results without showing obviously wrong suggestions - return results[lowest].concat( - results[lowest + 1] || [], - results[lowest + 2] || [], - ); + return results + .get(lowest) + .concat(results.get(lowest + 1), results.get(lowest + 2)); } /** * Get the help message to be displayed to the user if `--help` is passed. */ - getHelp() { - return getOptionsHelp(this); + getHelp(i18n: TranslationProxy) { + return getOptionsHelp(this, i18n); } } @@ -451,9 +466,7 @@ export function Option<K extends keyof TypeDocOptionMap>(name: K) { get(this: { application: Application } | { options: Options }) { const options = "options" in this ? this.options : this.application.options; - const value = options.getValue(name as keyof TypeDocOptions); - - return value as TypeDocOptionValues[K]; + return options.getValue(name); }, set(_value: never) { throw new Error( @@ -464,60 +477,6 @@ export function Option<K extends keyof TypeDocOptionMap>(name: K) { }; } -/** - * Binds an option to the given property. Does not register the option. - * - * Note: This is a legacy experimental decorator, and will not work with TS 5.0 decorators - * - * @since v0.16.3 - * @deprecated Will be removed in 0.26, use {@link Option | `@Option`} instead. - */ -export function BindOption<K extends keyof TypeDocOptionMap>( - name: K, -): <IK extends PropertyKey>( - target: ({ application: Application } | { options: Options }) & { - [K2 in IK]: TypeDocOptionValues[K]; - }, - key: IK, -) => void; - -/** - * Binds an option to the given property. Does not register the option. - * - * Note: This is a legacy experimental decorator, and will not work with TS 5.0 decorators - * - * @since v0.16.3 - * @deprecated Will be removed in 0.26, use {@link Option | `@Option`} instead - * - * @privateRemarks - * This overload is intended for plugin use only with looser type checks. Do not use internally. - */ -export function BindOption( - name: NeverIfInternal<string>, -): ( - target: { application: Application } | { options: Options }, - key: PropertyKey, -) => void; - -export function BindOption(name: string) { - return function ( - target: { application: Application } | { options: Options }, - key: PropertyKey, - ) { - Object.defineProperty(target, key, { - get(this: { application: Application } | { options: Options }) { - const options = - "options" in this ? this.options : this.application.options; - const value = options.getValue(name as keyof TypeDocOptions); - - return value; - }, - enumerable: true, - configurable: true, - }); - }; -} - // Based on https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows // Slightly modified for improved match results for options function editDistance(s: string, t: string): number { diff --git a/src/lib/utils/options/readers/arguments.ts b/src/lib/utils/options/readers/arguments.ts index 63e8b6dbd..dc5845a45 100644 --- a/src/lib/utils/options/readers/arguments.ts +++ b/src/lib/utils/options/readers/arguments.ts @@ -2,6 +2,7 @@ import { ok } from "assert"; import type { OptionsReader, Options } from ".."; import type { Logger } from "../../loggers"; import { ParameterType } from "../declaration"; +import type { TranslatedString } from "../../../internationalization/internationalization"; const ARRAY_OPTION_TYPES = new Set<ParameterType | undefined>([ ParameterType.Array, @@ -38,7 +39,7 @@ export class ArgumentsReader implements OptionsReader { options.setValue(name, value); } catch (err) { ok(err instanceof Error); - logger.error(err.message); + logger.error(err.message as TranslatedString); } }; @@ -51,7 +52,9 @@ export class ArgumentsReader implements OptionsReader { if (decl) { if (decl.configFileOnly) { logger.error( - `The '${decl.name}' option can only be specified via a config file.`, + logger.i18n.option_0_can_only_be_specified_by_config_file( + decl.name, + ), ); continue; } @@ -80,7 +83,9 @@ export class ArgumentsReader implements OptionsReader { if (index === this.args.length) { // Only boolean values have optional values. logger.warn( - `--${decl.name} expected a value, but none was given as an argument`, + logger.i18n.option_0_expected_a_value_but_none_provided( + decl.name, + ), ); } trySet(decl.name, this.args[index]); @@ -112,9 +117,10 @@ export class ArgumentsReader implements OptionsReader { } logger.error( - `Unknown option: ${name}, you may have meant:\n\t${options - .getSimilarOptions(name) - .join("\n\t")}`, + logger.i18n.unknown_option_0_may_have_meant_1( + name, + options.getSimilarOptions(name).join("\n\t"), + ), ); index++; } diff --git a/src/lib/utils/options/readers/package-json.ts b/src/lib/utils/options/readers/package-json.ts index 570230a4f..b4d5c2296 100644 --- a/src/lib/utils/options/readers/package-json.ts +++ b/src/lib/utils/options/readers/package-json.ts @@ -5,6 +5,7 @@ import { ok } from "assert"; import { nicePath } from "../../paths"; import { discoverPackageJson } from "../../fs"; import { dirname } from "path"; +import type { TranslatedString } from "../../../internationalization/internationalization"; export class PackageJsonReader implements OptionsReader { // Should run after the TypeDoc config reader but before the TS config @@ -25,11 +26,7 @@ export class PackageJsonReader implements OptionsReader { const { file, content } = result; if ("typedoc" in content) { - logger.warn( - `The 'typedoc' key in ${nicePath( - file, - )} was used by the legacy-packages entryPointStrategy and will be ignored.`, - ); + logger.warn(logger.i18n.typedoc_key_in_0_ignored(nicePath(file))); } const optsKey = "typedocOptions"; @@ -40,9 +37,7 @@ export class PackageJsonReader implements OptionsReader { const opts = content[optsKey]; if (opts === null || typeof opts !== "object") { logger.error( - `Failed to parse the "typedocOptions" field in ${nicePath( - file, - )}, ensure it exists and contains an object.`, + logger.i18n.typedoc_options_must_be_object_in_0(nicePath(file)), ); return; } @@ -52,7 +47,7 @@ export class PackageJsonReader implements OptionsReader { container.setValue(opt as never, val as never, dirname(file)); } catch (err) { ok(err instanceof Error); - logger.error(err.message); + logger.error(err.message as TranslatedString); } } } diff --git a/src/lib/utils/options/readers/tsconfig.ts b/src/lib/utils/options/readers/tsconfig.ts index a3e19a036..7f179049f 100644 --- a/src/lib/utils/options/readers/tsconfig.ts +++ b/src/lib/utils/options/readers/tsconfig.ts @@ -8,7 +8,7 @@ import { isFile } from "../../fs"; import { ok } from "assert"; import { additionalProperties, - Infer, + type Infer, isTagString, optional, validate, @@ -26,6 +26,7 @@ import { getTypeDocOptionsFromTsConfig, readTsConfig, } from "../../tsconfig"; +import type { TranslatedString } from "../../../internationalization/internationalization"; function isSupportForTags(obj: unknown): obj is Record<`@${string}`, boolean> { return ( @@ -83,7 +84,7 @@ export class TSConfigReader implements OptionsReader { // If the user didn't give us this option, we shouldn't complain about not being able to find it. if (container.isSet("tsconfig")) { logger.error( - `The tsconfig file ${nicePath(file)} does not exist`, + logger.i18n.tsconfig_file_0_does_not_exist(nicePath(file)), ); } return; @@ -105,18 +106,11 @@ export class TSConfigReader implements OptionsReader { const typedocOptions = getTypeDocOptionsFromTsConfig(fileToRead); if (typedocOptions.options) { - logger.error( - [ - "typedocOptions in tsconfig file specifies an option file to read but the option", - "file has already been read. This is likely a misconfiguration.", - ].join(" "), - ); + logger.error(logger.i18n.tsconfig_file_specifies_options_file()); delete typedocOptions.options; } if (typedocOptions.tsconfig) { - logger.error( - "typedocOptions in tsconfig file may not specify a tsconfig file to read", - ); + logger.error(logger.i18n.tsconfig_file_specifies_tsconfig_file()); delete typedocOptions.tsconfig; } @@ -135,7 +129,7 @@ export class TSConfigReader implements OptionsReader { ); } catch (error) { ok(error instanceof Error); - logger.error(error.message); + logger.error(error.message as TranslatedString); } } } @@ -156,8 +150,9 @@ export class TSConfigReader implements OptionsReader { ).filter((opt) => container.isSet(opt)); if (overwritten.length) { logger.warn( - `The ${overwritten.join(", ")} defined in typedoc.json will ` + - "be overwritten by configuration in tsdoc.json.", + logger.i18n.tags_0_defined_in_typedoc_json_overwritten_by_tsdoc_json( + overwritten.join(", "), + ), ); } @@ -199,9 +194,7 @@ export class TSConfigReader implements OptionsReader { private readTsDoc(logger: Logger, path: string): TsDocSchema | undefined { if (this.seenTsdocPaths.has(path)) { logger.error( - `Circular reference encountered for "extends" field of ${nicePath( - path, - )}`, + logger.i18n.circular_reference_extends_0(nicePath(path)), ); return; } @@ -213,16 +206,12 @@ export class TSConfigReader implements OptionsReader { ); if (error) { - logger.error( - `Failed to read tsdoc.json file at ${nicePath(path)}.`, - ); + logger.error(logger.i18n.failed_read_tsdoc_json_0(nicePath(path))); return; } if (!validate(tsDocSchema, config)) { - logger.error( - `The file ${nicePath(path)} is not a valid tsdoc.json file.`, - ); + logger.error(logger.i18n.invalid_tsdoc_json_0(nicePath(path))); return; } @@ -236,9 +225,10 @@ export class TSConfigReader implements OptionsReader { resolvedPath = resolver.resolve(extendedPath); } catch { logger.error( - `Failed to resolve ${extendedPath} to a file in ${nicePath( - path, - )}`, + logger.i18n.failed_resolve_0_to_file_in_1( + extendedPath, + nicePath(path), + ), ); return; } diff --git a/src/lib/utils/options/readers/typedoc.ts b/src/lib/utils/options/readers/typedoc.ts index 6344ca81a..75b5faa6e 100644 --- a/src/lib/utils/options/readers/typedoc.ts +++ b/src/lib/utils/options/readers/typedoc.ts @@ -10,6 +10,7 @@ import { nicePath, normalizePath } from "../../paths"; import { isFile } from "../../fs"; import { createRequire } from "module"; import { pathToFileURL } from "url"; +import type { TranslatedString } from "../../../internationalization/internationalization"; /** * Obtains option values from typedoc.json @@ -37,7 +38,7 @@ export class TypeDocReader implements OptionsReader { if (!file) { if (container.isSet("options")) { logger.error( - `The options file ${nicePath(path)} does not exist.`, + logger.i18n.options_file_0_does_not_exist(nicePath(path)), ); } return; @@ -61,25 +62,21 @@ export class TypeDocReader implements OptionsReader { ) { if (seen.has(file)) { logger.error( - `Tried to load the options file ${nicePath( - file, - )} multiple times.`, + logger.i18n.circular_reference_extends_0(nicePath(file)), ); return; } seen.add(file); let fileContent: any; - if (file.endsWith(".json")) { + if (file.endsWith(".json") || file.endsWith(".jsonc")) { const readResult = ts.readConfigFile(normalizePath(file), (path) => FS.readFileSync(path, "utf-8"), ); if (readResult.error) { logger.error( - `Failed to parse ${nicePath( - file, - )}, ensure it exists and contains an object.`, + logger.i18n.failed_read_options_file_0(nicePath(file)), ); return; } else { @@ -88,7 +85,6 @@ export class TypeDocReader implements OptionsReader { } else { try { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires fileContent = await require(file); } catch (error: any) { if (error?.code === "ERR_REQUIRE_ESM") { @@ -102,9 +98,12 @@ export class TypeDocReader implements OptionsReader { } } catch (error) { logger.error( - `Failed to read ${nicePath(file)}: ${ - error instanceof Error ? error.message : error - }`, + logger.i18n.failed_read_options_file_0(nicePath(file)), + ); + logger.error( + String( + error instanceof Error ? error.message : error, + ) as TranslatedString, ); return; } @@ -112,7 +111,7 @@ export class TypeDocReader implements OptionsReader { if (typeof fileContent !== "object" || !fileContent) { logger.error( - `The root value of ${nicePath(file)} is not an object.`, + logger.i18n.failed_read_options_file_0(nicePath(file)), ); return; } @@ -130,9 +129,10 @@ export class TypeDocReader implements OptionsReader { resolvedParent = resolver.resolve(extendedFile); } catch { logger.error( - `Failed to resolve ${extendedFile} to a file in ${nicePath( - file, - )}`, + logger.i18n.failed_resolve_0_to_file_in_1( + extendedFile, + nicePath(file), + ), ); continue; } @@ -150,7 +150,7 @@ export class TypeDocReader implements OptionsReader { ); } catch (error) { ok(error instanceof Error); - logger.error(error.message); + logger.error(error.message as TranslatedString); } } } @@ -160,7 +160,7 @@ export class TypeDocReader implements OptionsReader { * * @param path Path to the typedoc.(js|json) file. If path is a directory * typedoc file will be attempted to be found at the root of this path - * @return the typedoc.(js|json) file path or undefined + * @returns the typedoc.(js|json) file path or undefined */ private findTypedocFile(path: string): string | undefined { path = resolve(path); diff --git a/src/lib/utils/options/sources/typedoc.ts b/src/lib/utils/options/sources/typedoc.ts index 261d5402a..2f6d02e8f 100644 --- a/src/lib/utils/options/sources/typedoc.ts +++ b/src/lib/utils/options/sources/typedoc.ts @@ -6,13 +6,21 @@ import { EmitStrategy, CommentStyle, } from "../declaration"; -import { BUNDLED_THEMES, Theme } from "shiki"; import { SORT_STRATEGIES } from "../../sort"; import { EntryPointStrategy } from "../../entry-point"; import { ReflectionKind } from "../../../models/reflections/kind"; import * as Validation from "../../validation"; import { blockTags, inlineTags, modifierTags } from "../tsdoc-defaults"; import { getEnumKeys } from "../../enum"; +import type { + BundledLanguage, + BundledTheme, +} from "shiki" with { "resolution-mode": "import" }; +import { + getSupportedLanguagesWithoutAliases, + getSupportedThemes, +} from "../../highlighter"; +import { setDifference } from "../../set"; // For convenience, added in the same order as they are documented on the website. export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { @@ -23,26 +31,70 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ type: ParameterType.Path, name: "options", - help: "Specify a json option file that should be loaded. If not specified TypeDoc will look for 'typedoc.json' in the current directory.", + help: (i18n) => i18n.help_options(), hint: ParameterHint.File, defaultValue: "", }); options.addDeclaration({ type: ParameterType.Path, name: "tsconfig", - help: "Specify a TypeScript config file that should be loaded. If not specified TypeDoc will look for 'tsconfig.json' in the current directory.", + help: (i18n) => i18n.help_tsconfig(), hint: ParameterHint.File, defaultValue: "", }); options.addDeclaration({ name: "compilerOptions", - help: "Selectively override the TypeScript compiler options used by TypeDoc.", + help: (i18n) => i18n.help_compilerOptions(), + type: ParameterType.Mixed, + configFileOnly: true, + validate(value, i18n) { + if (!Validation.validate({}, value)) { + throw new Error( + i18n.option_0_must_be_an_object("compilerOptions"), + ); + } + }, + }); + options.addDeclaration({ + name: "lang", + help: (i18n) => i18n.help_lang(), + type: ParameterType.String, + defaultValue: "en", + }); + options.addDeclaration({ + name: "locales", + help: (i18n) => i18n.help_locales(), + type: ParameterType.Mixed, + configFileOnly: true, + defaultValue: {}, + validate(value, i18n) { + if (typeof value !== "object" || !value) { + throw new Error(i18n.locales_must_be_an_object()); + } + + for (const val of Object.values(value)) { + if (typeof val !== "object" || !val) { + throw new Error(i18n.locales_must_be_an_object()); + } + + for (const val2 of Object.values(val)) { + if (typeof val2 !== "string") { + throw new Error(i18n.locales_must_be_an_object()); + } + } + } + }, + }); + options.addDeclaration({ + name: "packageOptions", + help: (i18n) => i18n.help_packageOptions(), type: ParameterType.Mixed, configFileOnly: true, - validate(value) { + defaultValue: {}, + validate(value, i18n) { if (!Validation.validate({}, value)) { throw new Error( - "The 'compilerOptions' option must be a non-array object.", + i18n.option_0_must_be_an_object("packageOptions"), ); } }, @@ -54,43 +106,53 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "entryPoints", - help: "The entry points of your documentation.", + help: (i18n) => i18n.help_entryPoints(), type: ParameterType.GlobArray, }); options.addDeclaration({ name: "entryPointStrategy", - help: "The strategy to be used to convert entry points into documentation modules.", + help: (i18n) => i18n.help_entryPointStrategy(), type: ParameterType.Map, map: EntryPointStrategy, defaultValue: EntryPointStrategy.Resolve, }); + options.addDeclaration({ + name: "alwaysCreateEntryPointModule", + help: (i18n) => i18n.help_alwaysCreateEntryPointModule(), + type: ParameterType.Boolean, + }); + options.addDeclaration({ + name: "projectDocuments", + help: (i18n) => i18n.help_projectDocuments(), + type: ParameterType.GlobArray, + }); options.addDeclaration({ name: "exclude", - help: "Define patterns to be excluded when expanding a directory that was specified as an entry point.", + help: (i18n) => i18n.help_exclude(), type: ParameterType.GlobArray, }); options.addDeclaration({ name: "externalPattern", - help: "Define patterns for files that should be considered being external.", + help: (i18n) => i18n.help_externalPattern(), type: ParameterType.GlobArray, defaultValue: ["**/node_modules/**"], }); options.addDeclaration({ name: "excludeExternals", - help: "Prevent externally resolved symbols from being documented.", + help: (i18n) => i18n.help_excludeExternals(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "excludeNotDocumented", - help: "Prevent symbols that are not explicitly documented from appearing in the results.", + help: (i18n) => i18n.help_excludeNotDocumented(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "excludeNotDocumentedKinds", - help: "Specify the type of reflections that can be removed by excludeNotDocumented.", + help: (i18n) => i18n.help_excludeNotDocumentedKinds(), type: ParameterType.Array, - validate(value) { + validate(value, i18n) { const invalid = new Set(value); const valid = new Set(getEnumKeys(ReflectionKind)); for (const notPermitted of [ @@ -107,11 +169,10 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { if (invalid.size !== 0) { throw new Error( - `excludeNotDocumentedKinds may only specify known values, and invalid values were provided (${Array.from( - invalid, - ).join(", ")}). The valid kinds are:\n${Array.from( - valid, - ).join(", ")}`, + i18n.exclude_not_documented_specified_0_valid_values_are_1( + Array.from(invalid).join(", "), + Array.from(valid).join(", "), + ), ); } }, @@ -139,68 +200,60 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { }); options.addDeclaration({ name: "excludeInternal", - help: "Prevent symbols that are marked with @internal from being documented.", + help: (i18n) => i18n.help_excludeInternal(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "excludeCategories", - help: "Exclude symbols within this category from the documentation.", + help: (i18n) => i18n.help_excludeCategories(), type: ParameterType.Array, defaultValue: [], }); options.addDeclaration({ name: "excludePrivate", - help: "Ignore private variables and methods.", + help: (i18n) => i18n.help_excludePrivate(), type: ParameterType.Boolean, + defaultValue: true, }); options.addDeclaration({ name: "excludeProtected", - help: "Ignore protected variables and methods.", + help: (i18n) => i18n.help_excludeProtected(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "excludeReferences", - help: "If a symbol is exported multiple times, ignore all but the first export.", + help: (i18n) => i18n.help_excludeReferences(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "externalSymbolLinkMappings", - help: "Define custom links for symbols not included in the documentation.", + help: (i18n) => i18n.help_externalSymbolLinkMappings(), type: ParameterType.Mixed, defaultValue: {}, - validate(value) { - const error = - "externalSymbolLinkMappings must be a Record<package name, Record<symbol name, link>>"; - + validate(value, i18n) { if (!Validation.validate({}, value)) { - throw new Error(error); + throw new Error( + i18n.external_symbol_link_mappings_must_be_object(), + ); } for (const mappings of Object.values(value)) { if (!Validation.validate({}, mappings)) { - throw new Error(error); + throw new Error( + i18n.external_symbol_link_mappings_must_be_object(), + ); } for (const link of Object.values(mappings)) { if (typeof link !== "string") { - throw new Error(error); + throw new Error( + i18n.external_symbol_link_mappings_must_be_object(), + ); } } } }, }); - options.addDeclaration({ - name: "media", - help: "Specify the location with media files that should be copied to the output directory.", - type: ParameterType.Path, - hint: ParameterHint.Directory, - }); - options.addDeclaration({ - name: "includes", - help: "Specify the location to look for included documents (use [[include:FILENAME]] in comments).", - type: ParameterType.Path, - hint: ParameterHint.Directory, - }); /////////////////////////// ///// Output Options ////// @@ -208,66 +261,98 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "out", - help: "Specify the location the documentation should be written to.", + help: (i18n) => i18n.help_out(), type: ParameterType.Path, hint: ParameterHint.Directory, defaultValue: "./docs", }); options.addDeclaration({ name: "json", - help: "Specify the location and filename a JSON file describing the project is written to.", + help: (i18n) => i18n.help_json(), type: ParameterType.Path, hint: ParameterHint.File, }); options.addDeclaration({ name: "pretty", - help: "Specify whether the output JSON should be formatted with tabs.", + help: (i18n) => i18n.help_pretty(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "emit", - help: "Specify what TypeDoc should emit, 'docs', 'both', or 'none'.", + help: (i18n) => i18n.help_emit(), type: ParameterType.Map, map: EmitStrategy, defaultValue: "docs", }); options.addDeclaration({ name: "theme", - help: "Specify the theme name to render the documentation with", + help: (i18n) => i18n.help_theme(), type: ParameterType.String, defaultValue: "default", }); - const defaultLightTheme: Theme = "light-plus"; - const defaultDarkTheme: Theme = "dark-plus"; + const defaultLightTheme: BundledTheme = "light-plus"; + const defaultDarkTheme: BundledTheme = "dark-plus"; options.addDeclaration({ name: "lightHighlightTheme", - help: "Specify the code highlighting theme in light mode.", + help: (i18n) => i18n.help_lightHighlightTheme(), type: ParameterType.String, defaultValue: defaultLightTheme, - validate(value) { - if (!(BUNDLED_THEMES as readonly string[]).includes(value)) { + validate(value, i18n) { + if (!getSupportedThemes().includes(value)) { throw new Error( - `lightHighlightTheme must be one of the following: ${BUNDLED_THEMES.join( - ", ", - )}`, + i18n.highlight_theme_0_must_be_one_of_1( + "lightHighlightTheme", + getSupportedThemes().join(", "), + ), ); } }, }); options.addDeclaration({ name: "darkHighlightTheme", - help: "Specify the code highlighting theme in dark mode.", + help: (i18n) => i18n.help_darkHighlightTheme(), type: ParameterType.String, defaultValue: defaultDarkTheme, - validate(value) { - if (!(BUNDLED_THEMES as readonly string[]).includes(value)) { + validate(value, i18n) { + if (!getSupportedThemes().includes(value)) { + throw new Error( + i18n.highlight_theme_0_must_be_one_of_1( + "darkHighlightTheme", + getSupportedThemes().join(", "), + ), + ); + } + }, + }); + options.addDeclaration({ + name: "highlightLanguages", + help: (i18n) => i18n.help_highlightLanguages(), + type: ParameterType.Array, + defaultValue: [ + "bash", + "console", + "css", + "html", + "javascript", + "json", + "jsonc", + "json5", + "tsx", + "typescript", + ] satisfies BundledLanguage[], + validate(value, i18n) { + const invalid = setDifference( + value, + getSupportedLanguagesWithoutAliases(), + ); + if (invalid.size) { throw new Error( - `darkHighlightTheme must be one of the following: ${BUNDLED_THEMES.join( - ", ", - )}`, + i18n.highlightLanguages_contains_invalid_languages_0( + Array.from(invalid).join(", "), + ), ); } }, @@ -275,68 +360,86 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "customCss", - help: "Path to a custom CSS file to for the theme to import.", + help: (i18n) => i18n.help_customCss(), type: ParameterType.Path, }); options.addDeclaration({ - name: "markedOptions", - help: "Specify the options passed to Marked, the Markdown parser used by TypeDoc.", + name: "markdownItOptions", + help: (i18n) => i18n.help_markdownItOptions(), type: ParameterType.Mixed, configFileOnly: true, - validate(value) { + defaultValue: { + html: true, + linkify: true, + }, + validate(value, i18n) { if (!Validation.validate({}, value)) { throw new Error( - "The 'markedOptions' option must be a non-array object.", + i18n.option_0_must_be_an_object("markdownItOptions"), + ); + } + }, + }); + options.addDeclaration({ + name: "markdownItLoader", + help: (i18n) => i18n.help_markdownItLoader(), + type: ParameterType.Mixed, + configFileOnly: true, + defaultValue: () => {}, + validate(value, i18n) { + if (typeof value !== "function") { + throw new Error( + i18n.option_0_must_be_a_function("markdownItLoader"), ); } }, }); options.addDeclaration({ name: "maxTypeConversionDepth", - help: "Set the maximum depth of types to be converted.", + help: (i18n) => i18n.help_maxTypeConversionDepth(), defaultValue: 10, type: ParameterType.Number, }); options.addDeclaration({ name: "name", - help: "Set the name of the project that will be used in the header of the template.", + help: (i18n) => i18n.help_name(), }); options.addDeclaration({ name: "includeVersion", - help: "Add the package version to the project name.", + help: (i18n) => i18n.help_includeVersion(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "disableSources", - help: "Disable setting the source of a reflection when documenting it.", + help: (i18n) => i18n.help_disableSources(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "sourceLinkTemplate", - help: "Specify a link template to be used when generating source urls. If not set, will be automatically created using the git remote. Supports {path}, {line}, {gitRevision} placeholders.", + help: (i18n) => i18n.help_sourceLinkTemplate(), }); options.addDeclaration({ name: "gitRevision", - help: "Use specified revision instead of the last revision for linking to GitHub/Bitbucket source files. Has no effect if disableSources is set.", + help: (i18n) => i18n.help_gitRevision(), }); options.addDeclaration({ name: "gitRemote", - help: "Use the specified remote for linking to GitHub/Bitbucket source files. Has no effect if disableGit or disableSources is set.", + help: (i18n) => i18n.help_gitRemote(), defaultValue: "origin", }); options.addDeclaration({ name: "disableGit", - help: "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: (i18n) => i18n.help_disableGit(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "basePath", - help: "Specifies the base path to be used when displaying file paths.", + help: (i18n) => i18n.help_basePath(), type: ParameterType.Path, }); options.addDeclaration({ name: "excludeTags", - help: "Remove the listed block/modifier tags from doc comments.", + help: (i18n) => i18n.help_excludeTags(), type: ParameterType.Array, defaultValue: [ "@override", @@ -345,150 +448,152 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { "@satisfies", "@overload", ], - validate(value) { + validate(value, i18n) { if (!Validation.validate([Array, Validation.isTagString], value)) { throw new Error( - `excludeTags must be an array of valid tag names.`, + i18n.option_0_values_must_be_array_of_tags("excludeTags"), ); } }, }); options.addDeclaration({ name: "readme", - help: "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: (i18n) => i18n.help_readme(), type: ParameterType.Path, }); - options.addDeclaration({ - name: "stripYamlFrontmatter", - help: "Strip YAML frontmatter from markdown files.", - type: ParameterType.Boolean, - }); options.addDeclaration({ name: "cname", - help: "Set the CNAME file text, it's useful for custom domains on GitHub Pages.", + help: (i18n) => i18n.help_cname(), }); options.addDeclaration({ name: "sourceLinkExternal", - help: "Specifies that source links should be treated as external links to be opened in a new tab.", + help: (i18n) => i18n.help_sourceLinkExternal(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "githubPages", - help: "Generate a .nojekyll file to prevent 404 errors in GitHub Pages. Defaults to `true`.", + help: (i18n) => i18n.help_githubPages(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ - name: "sitemapBaseUrl", - help: "Specify a base URL to be used in generating a sitemap.xml in our output folder. If not specified, no sitemap will be generated.", - validate(value) { + name: "hostedBaseUrl", + help: (i18n) => i18n.help_hostedBaseUrl(), + validate(value, i18n) { if (!/https?:\/\//.test(value)) { - throw new Error( - "sitemapBaseUrl must start with http:// or https://", - ); + throw new Error(i18n.hostedBaseUrl_must_start_with_http()); } }, }); options.addDeclaration({ - name: "htmlLang", - help: "Sets the lang attribute in the generated html tag.", - type: ParameterType.String, - defaultValue: "en", + name: "useHostedBaseUrlForAbsoluteLinks", + help: (i18n) => i18n.help_useHostedBaseUrlForAbsoluteLinks(), + type: ParameterType.Boolean, + }); + options.addDeclaration({ + name: "hideGenerator", + help: (i18n) => i18n.help_hideGenerator(), + type: ParameterType.Boolean, }); options.addDeclaration({ - name: "gaID", - help: "Set the Google Analytics tracking ID and activate tracking code.", + name: "customFooterHtml", + help: (i18n) => i18n.help_customFooterHtml(), + type: ParameterType.String, }); options.addDeclaration({ - name: "hideGenerator", - help: "Do not print the TypeDoc link at the end of the page.", + name: "customFooterHtmlDisableWrapper", + help: (i18n) => i18n.help_customFooterHtmlDisableWrapper(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "hideParameterTypesInTitle", - help: "Hides parameter types in signature titles for easier scanning.", + help: (i18n) => i18n.help_hideParameterTypesInTitle(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "cacheBust", - help: "Include the generation time in links to static assets.", + help: (i18n) => i18n.help_cacheBust(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "searchInComments", - help: "If set, the search index will also include comments. This will greatly increase the size of the search index.", + help: (i18n) => i18n.help_searchInComments(), + type: ParameterType.Boolean, + }); + options.addDeclaration({ + name: "searchInDocuments", + help: (i18n) => i18n.help_searchInDocuments(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "cleanOutputDir", - help: "If set, TypeDoc will remove the output directory before writing output.", + help: (i18n) => i18n.help_cleanOutputDir(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "titleLink", - help: "Set the link the title in the header points to. Defaults to the documentation homepage.", + help: (i18n) => i18n.help_titleLink(), type: ParameterType.String, }); options.addDeclaration({ name: "navigationLinks", - help: "Defines links to be included in the header.", + help: (i18n) => i18n.help_navigationLinks(), type: ParameterType.Mixed, defaultValue: {}, - validate(value) { + validate(value, i18n) { if (!isObject(value)) { throw new Error( - `navigationLinks must be an object with string labels as keys and URL values.`, + i18n.option_0_must_be_object_with_urls("navigationLinks"), ); } if (Object.values(value).some((x) => typeof x !== "string")) { throw new Error( - `All values of navigationLinks must be string URLs.`, + i18n.option_0_must_be_object_with_urls("navigationLinks"), ); } }, }); options.addDeclaration({ name: "sidebarLinks", - help: "Defines links to be included in the sidebar.", + help: (i18n) => i18n.help_sidebarLinks(), type: ParameterType.Mixed, defaultValue: {}, - validate(value) { + validate(value, i18n) { if (!isObject(value)) { throw new Error( - `sidebarLinks must be an object with string labels as keys and URL values.`, + i18n.option_0_must_be_object_with_urls("sidebarLinks"), ); } if (Object.values(value).some((x) => typeof x !== "string")) { throw new Error( - `All values of sidebarLinks must be string URLs.`, + i18n.option_0_must_be_object_with_urls("sidebarLinks"), ); } }, }); options.addDeclaration({ name: "navigationLeaves", - help: "Branches of the navigation tree which should not be expanded.", + help: (i18n) => i18n.help_navigationLeaves(), type: ParameterType.Array, }); options.addDeclaration({ name: "navigation", - help: "Determines how the navigation sidebar is organized.", + help: (i18n) => i18n.help_navigation(), type: ParameterType.Flags, defaults: { includeCategories: false, includeGroups: false, includeFolders: true, - fullTree: false, }, }); options.addDeclaration({ name: "visibilityFilters", - help: "Specify the default visibility for builtin filters and additional filters according to modifier tags.", + help: (i18n) => i18n.help_visibilityFilters(), type: ParameterType.Mixed, configFileOnly: true, defaultValue: { @@ -497,25 +602,25 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { inherited: true, external: false, }, - validate(value) { + validate(value, i18n) { const knownKeys = ["protected", "private", "inherited", "external"]; if (!value || typeof value !== "object") { - throw new Error("visibilityFilters must be an object."); + throw new Error( + i18n.option_0_must_be_an_object("visibilityFilters"), + ); } for (const [key, val] of Object.entries(value)) { if (!key.startsWith("@") && !knownKeys.includes(key)) { throw new Error( - `visibilityFilters can only include the following non-@ keys: ${knownKeys.join( - ", ", - )}`, + i18n.visibility_filters_only_include_0( + knownKeys.join(", "), + ), ); } if (typeof val !== "boolean") { - throw new Error( - `All values of visibilityFilters must be booleans.`, - ); + throw new Error(i18n.visibility_filters_must_be_booleans()); } } }, @@ -523,40 +628,42 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "searchCategoryBoosts", - help: "Configure search to give a relevance boost to selected categories", + help: (i18n) => i18n.help_searchCategoryBoosts(), type: ParameterType.Mixed, configFileOnly: true, defaultValue: {}, - validate(value) { + validate(value, i18n) { if (!isObject(value)) { throw new Error( - "The 'searchCategoryBoosts' option must be a non-array object.", + i18n.option_0_must_be_an_object("searchCategoryBoosts"), ); } if (Object.values(value).some((x) => typeof x !== "number")) { throw new Error( - "All values of 'searchCategoryBoosts' must be numbers.", + i18n.option_0_values_must_be_numbers( + "searchCategoryBoosts", + ), ); } }, }); options.addDeclaration({ name: "searchGroupBoosts", - help: 'Configure search to give a relevance boost to selected kinds (eg "class")', + help: (i18n) => i18n.help_searchGroupBoosts(), type: ParameterType.Mixed, configFileOnly: true, defaultValue: {}, - validate(value: unknown) { + validate(value, i18n) { if (!isObject(value)) { throw new Error( - "The 'searchGroupBoosts' option must be a non-array object.", + i18n.option_0_must_be_an_object("searchGroupBoosts"), ); } if (Object.values(value).some((x) => typeof x !== "number")) { throw new Error( - "All values of 'searchGroupBoosts' must be numbers.", + i18n.option_0_values_must_be_numbers("searchGroupBoosts"), ); } }, @@ -568,7 +675,7 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "jsDocCompatibility", - help: "Sets compatibility options for comment parsing that increase similarity with JSDoc comments.", + help: (i18n) => i18n.help_jsDocCompatibility(), type: ParameterType.Flags, defaults: { defaultTag: true, @@ -578,9 +685,15 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { }, }); + options.addDeclaration({ + name: "suppressCommentWarningsInDeclarationFiles", + help: (i18n) => i18n.help_lang(), + type: ParameterType.Boolean, + }); + options.addDeclaration({ name: "commentStyle", - help: "Determines how TypeDoc searches for comments.", + help: (i18n) => i18n.help_commentStyle(), type: ParameterType.Map, map: CommentStyle, defaultValue: CommentStyle.JSDoc, @@ -588,52 +701,67 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "useTsLinkResolution", - help: "Use TypeScript's link resolution when determining where @link tags point. This only applies to JSDoc style comments.", + help: (i18n) => i18n.help_useTsLinkResolution(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "preserveLinkText", - help: "If set, @link tags without link text will use the text content as the link. If not set, will use the target reflection name.", + help: (i18n) => i18n.help_preserveLinkText(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "blockTags", - help: "Block tags which TypeDoc should recognize when parsing comments.", + help: (i18n) => i18n.help_blockTags(), type: ParameterType.Array, defaultValue: blockTags, - validate(value) { + validate(value, i18n) { if (!Validation.validate([Array, Validation.isTagString], value)) { throw new Error( - `blockTags must be an array of valid tag names.`, + i18n.option_0_values_must_be_array_of_tags("blockTags"), ); } }, }); options.addDeclaration({ name: "inlineTags", - help: "Inline tags which TypeDoc should recognize when parsing comments.", + help: (i18n) => i18n.help_inlineTags(), type: ParameterType.Array, defaultValue: inlineTags, - validate(value) { + validate(value, i18n) { if (!Validation.validate([Array, Validation.isTagString], value)) { throw new Error( - `inlineTags must be an array of valid tag names.`, + i18n.option_0_values_must_be_array_of_tags("inlineTags"), ); } }, }); options.addDeclaration({ name: "modifierTags", - help: "Modifier tags which TypeDoc should recognize when parsing comments.", + help: (i18n) => i18n.help_modifierTags(), type: ParameterType.Array, defaultValue: modifierTags, - validate(value) { + validate(value, i18n) { + if (!Validation.validate([Array, Validation.isTagString], value)) { + throw new Error( + i18n.option_0_values_must_be_array_of_tags("modifierTags"), + ); + } + }, + }); + options.addDeclaration({ + name: "cascadedModifierTags", + help: (i18n) => i18n.help_modifierTags(), + type: ParameterType.Array, + defaultValue: ["@alpha", "@beta", "@experimental"], + validate(value, i18n) { if (!Validation.validate([Array, Validation.isTagString], value)) { throw new Error( - `modifierTags must be an array of valid tag names.`, + i18n.option_0_values_must_be_array_of_tags( + "cascadedModifierTags", + ), ); } }, @@ -645,52 +773,32 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "categorizeByGroup", - help: "Specify whether categorization will be done at the group level.", + help: (i18n) => i18n.help_categorizeByGroup(), type: ParameterType.Boolean, defaultValue: false, }); options.addDeclaration({ name: "defaultCategory", - help: "Specify the default category for reflections without a category.", + help: (i18n) => i18n.help_defaultCategory(), defaultValue: "Other", }); options.addDeclaration({ name: "categoryOrder", - help: "Specify the order in which categories appear. * indicates the relative order for categories not in the list.", + help: (i18n) => i18n.help_categoryOrder(), type: ParameterType.Array, }); options.addDeclaration({ name: "groupOrder", - help: "Specify the order in which groups appear. * indicates the relative order for groups not in the list.", + help: (i18n) => i18n.help_groupOrder(), type: ParameterType.Array, - // Defaults to the same as the defaultKindSortOrder in sort.ts - defaultValue: [ - ReflectionKind.Reference, - // project is never a child so never added to a group - ReflectionKind.Module, - ReflectionKind.Namespace, - ReflectionKind.Enum, - ReflectionKind.EnumMember, - ReflectionKind.Class, - ReflectionKind.Interface, - ReflectionKind.TypeAlias, - - ReflectionKind.Constructor, - ReflectionKind.Property, - ReflectionKind.Variable, - ReflectionKind.Function, - ReflectionKind.Accessor, - ReflectionKind.Method, - - // others are never added to groups - ].map(ReflectionKind.pluralString), + // default order specified in GroupPlugin to correctly handle localization. }); options.addDeclaration({ name: "sort", - help: "Specify the sort strategy for documented values.", + help: (i18n) => i18n.help_sort(), type: ParameterType.Array, defaultValue: ["kind", "instance-first", "alphabetical"], - validate(value) { + validate(value, i18n) { const invalid = new Set(value); for (const v of SORT_STRATEGIES) { invalid.delete(v); @@ -698,29 +806,27 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { if (invalid.size !== 0) { throw new Error( - `sort may only specify known values, and invalid values were provided (${Array.from( - invalid, - ).join( - ", ", - )}). The valid sort strategies are:\n${SORT_STRATEGIES.join( - ", ", - )}`, + i18n.option_0_specified_1_but_only_2_is_valid( + "sort", + Array.from(invalid).join(", "), + SORT_STRATEGIES.join(", "), + ), ); } }, }); options.addDeclaration({ name: "sortEntryPoints", - help: "If set, entry points will be subject to the same sorting rules as other reflections.", + help: (i18n) => i18n.help_sortEntryPoints(), type: ParameterType.Boolean, defaultValue: true, }); options.addDeclaration({ name: "kindSortOrder", - help: "Specify the sort order for reflections when 'kind' is specified.", + help: (i18n) => i18n.help_kindSortOrder(), type: ParameterType.Array, defaultValue: [], - validate(value) { + validate(value, i18n) { const invalid = new Set(value); const valid = getEnumKeys(ReflectionKind); for (const v of valid) { @@ -729,9 +835,11 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { if (invalid.size !== 0) { throw new Error( - `kindSortOrder may only specify known values, and invalid values were provided (${Array.from( - invalid, - ).join(", ")}). The valid kinds are:\n${valid.join(", ")}`, + i18n.option_0_specified_1_but_only_2_is_valid( + `kindSortOrder`, + Array.from(invalid).join(", "), + valid.join(", "), + ), ); } }, @@ -743,44 +851,44 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "watch", - help: "Watch files for changes and rebuild docs on change.", + help: (i18n) => i18n.help_watch(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "preserveWatchOutput", - help: "If set, TypeDoc will not clear the screen between compilation runs.", + help: (i18n) => i18n.help_preserveWatchOutput(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "skipErrorChecking", - help: "Do not run TypeScript's type checking before generating docs.", + help: (i18n) => i18n.help_skipErrorChecking(), type: ParameterType.Boolean, defaultValue: false, }); options.addDeclaration({ name: "help", - help: "Print this message.", + help: (i18n) => i18n.help_help(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "version", - help: "Print TypeDoc's version.", + help: (i18n) => i18n.help_version(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "showConfig", - help: "Print the resolved configuration and exit.", + help: (i18n) => i18n.help_showConfig(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "plugin", - help: "Specify the npm plugins that should be loaded. Omit to load all installed plugins.", + help: (i18n) => i18n.help_plugin(), type: ParameterType.ModuleArray, }); options.addDeclaration({ name: "logLevel", - help: "Specify what level of logging should be used.", + help: (i18n) => i18n.help_logLevel(), type: ParameterType.Map, map: LogLevel, defaultValue: LogLevel.Info, @@ -788,33 +896,35 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "treatWarningsAsErrors", - help: "If set, all warnings will be treated as errors.", + help: (i18n) => i18n.help_treatWarningsAsErrors(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "treatValidationWarningsAsErrors", - help: "If set, warnings emitted during validation will be treated as errors. This option cannot be used to disable treatWarningsAsErrors for validation warnings.", + help: (i18n) => i18n.help_treatValidationWarningsAsErrors(), type: ParameterType.Boolean, }); options.addDeclaration({ name: "intentionallyNotExported", - help: "A list of types which should not produce 'referenced but not documented' warnings.", + help: (i18n) => i18n.help_intentionallyNotExported(), type: ParameterType.Array, }); options.addDeclaration({ name: "requiredToBeDocumented", - help: "A list of reflection kinds that must be documented", + help: (i18n) => i18n.help_requiredToBeDocumented(), type: ParameterType.Array, - validate(values) { + validate(values, i18n) { // this is good enough because the values of the ReflectionKind enum are all numbers const validValues = getEnumKeys(ReflectionKind); for (const kind of values) { if (!validValues.includes(kind)) { throw new Error( - `'${kind}' is an invalid value for 'requiredToBeDocumented'. Must be one of: ${validValues.join( - ", ", - )}`, + i18n.option_0_specified_1_but_only_2_is_valid( + "requiredToBeDocumented", + kind, + validValues.join(", "), + ), ); } } @@ -835,7 +945,7 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) { options.addDeclaration({ name: "validation", - help: "Specify which validation steps TypeDoc should perform on your generated documentation.", + help: (i18n) => i18n.help_validation(), type: ParameterType.Flags, defaults: { notExported: true, diff --git a/src/lib/utils/options/tsdoc-defaults.ts b/src/lib/utils/options/tsdoc-defaults.ts index 9ae30e874..17a7e23c9 100644 --- a/src/lib/utils/options/tsdoc-defaults.ts +++ b/src/lib/utils/options/tsdoc-defaults.ts @@ -1,66 +1,79 @@ // If updating these lists, also update tsdoc.json export const tsdocBlockTags = [ + "@defaultValue", "@deprecated", + "@example", "@param", + "@privateRemarks", "@remarks", "@returns", + "@see", "@throws", - "@privateRemarks", - "@defaultValue", "@typeParam", ] as const; export const blockTags = [ ...tsdocBlockTags, - "@module", - "@inheritDoc", - "@group", - "@groupDescription", + "@author", + "@callback", "@category", "@categoryDescription", - // Alias for @typeParam - "@template", - // Because TypeScript is important! - "@type", - "@typedef", - "@callback", + "@default", + "@document", + "@group", + "@groupDescription", + "@import", + "@inheritDoc", + "@jsx", + "@license", + "@module", "@prop", "@property", + "@return", "@satisfies", + "@since", + "@template", // Alias for @typeParam + "@type", + "@typedef", ] as const; export const tsdocInlineTags = ["@link", "@inheritDoc", "@label"] as const; -export const inlineTags = [...tsdocInlineTags, "@linkcode", "@linkplain"]; +export const inlineTags = [ + ...tsdocInlineTags, + "@linkcode", + "@linkplain", +] as const; export const tsdocModifierTags = [ - "@public", - "@private", - "@protected", - "@internal", - "@readonly", - "@packageDocumentation", - "@eventProperty", "@alpha", "@beta", + "@eventProperty", "@experimental", - "@sealed", + "@internal", "@override", + "@packageDocumentation", + "@public", + "@readonly", + "@sealed", "@virtual", ] as const; export const modifierTags = [ ...tsdocModifierTags, - "@hidden", - "@ignore", "@class", "@enum", "@event", - "@overload", - "@namespace", + "@hidden", + "@hideCategories", + "@hideconstructor", + "@hideGroups", + "@ignore", "@interface", + "@namespace", + "@overload", + "@private", + "@protected", "@showCategories", - "@hideCategories", "@showGroups", - "@hideGroups", ] as const; diff --git a/src/lib/utils/package-manifest.ts b/src/lib/utils/package-manifest.ts index 00c70f2fe..bd390acc3 100644 --- a/src/lib/utils/package-manifest.ts +++ b/src/lib/utils/package-manifest.ts @@ -29,7 +29,9 @@ export function loadPackageManifest( ): Record<string, unknown> | undefined { const packageJson: unknown = JSON.parse(readFile(packageJsonPath)); if (typeof packageJson !== "object" || !packageJson) { - logger.error(`The file ${packageJsonPath} is not an object.`); + logger.error( + logger.i18n.file_0_not_an_object(nicePath(packageJsonPath)), + ); return undefined; } return packageJson as Record<string, unknown>; @@ -66,7 +68,7 @@ function getPackagePaths( } /** - * Given a list of (potentially wildcarded) package paths, + * Given a list of (potentially wildcard containing) package paths, * return all the actual package folders found. */ export function expandPackages( @@ -81,35 +83,34 @@ export function expandPackages( // be dealing with either a root or a leaf. So let's do this recursively, // as it actually is simpler from an implementation perspective anyway. return workspaces.flatMap((workspace) => { - const globbedPackageJsonPaths = glob( + const expandedPackageJsonPaths = glob( resolve(packageJsonDir, workspace, "package.json"), resolve(packageJsonDir), ); - if (globbedPackageJsonPaths.length === 0) { + if (expandedPackageJsonPaths.length === 0) { logger.warn( - `The entrypoint glob ${nicePath( - workspace, - )} did not match any directories containing package.json.`, + logger.i18n.entry_point_0_did_not_match_any_packages( + nicePath(workspace), + ), ); } else { logger.verbose( `Expanded ${nicePath( workspace, - )} to:\n\t${globbedPackageJsonPaths + )} to:\n\t${expandedPackageJsonPaths .map(nicePath) .join("\n\t")}`, ); } - return globbedPackageJsonPaths.flatMap((packageJsonPath) => { + return expandedPackageJsonPaths.flatMap((packageJsonPath) => { if (matchesAny(exclude, dirname(packageJsonPath))) { return []; } const packageJson = loadPackageManifest(logger, packageJsonPath); if (packageJson === undefined) { - logger.error(`Failed to load ${packageJsonPath}`); return []; } const packagePaths = getPackagePaths(packageJson); diff --git a/src/lib/utils/paths.ts b/src/lib/utils/paths.ts index cfef4e857..2f25c4192 100644 --- a/src/lib/utils/paths.ts +++ b/src/lib/utils/paths.ts @@ -37,5 +37,21 @@ export function nicePath(absPath: string) { * @returns The normalized path. */ export function normalizePath(path: string) { - return path.replace(/\\/g, "/"); + if (process.platform === "win32") { + // Ensure forward slashes + path = path.replace(/\\/g, "/"); + + // Msys2 git on windows will give paths which use unix-style + // absolute paths, like /c/users/you. Since the rest of TypeDoc + // expects drive letters, convert it to that here. + path = path.replace(/^\/([a-zA-Z])\//, (_m, m1: string) => `${m1}:/`); + + // Make Windows drive letters upper case + path = path.replace( + /^([^:]+):\//, + (_m, m1: string) => m1.toUpperCase() + ":/", + ); + } + + return path; } diff --git a/src/lib/utils/perf.ts b/src/lib/utils/perf.ts index e63dfcbca..1950feaf3 100644 --- a/src/lib/utils/perf.ts +++ b/src/lib/utils/perf.ts @@ -50,7 +50,9 @@ export function Bench<T extends Function>( let runner: T | undefined; return function (this: any, ...args: any) { if (!runner) { - const className = Object.getPrototypeOf(this).constructor.name; + const className = context.static + ? this.name + : Object.getPrototypeOf(this).constructor.name; runner = bench(value, `${className}.${String(context.name)}`); } return runner.apply(this, args); diff --git a/src/lib/utils/plugins.ts b/src/lib/utils/plugins.ts index 25019cb72..9bf43dd51 100644 --- a/src/lib/utils/plugins.ts +++ b/src/lib/utils/plugins.ts @@ -3,6 +3,7 @@ import { pathToFileURL } from "url"; import type { Application } from "../application"; import { nicePath } from "./paths"; +import type { TranslatedString } from "../internationalization/internationalization"; export async function loadPlugins( app: Application, @@ -12,7 +13,6 @@ export async function loadPlugins( const pluginDisplay = getPluginDisplayName(plugin); try { - // eslint-disable-next-line @typescript-eslint/no-var-requires let instance: any; try { instance = require(plugin); @@ -32,18 +32,20 @@ export async function loadPlugins( if (typeof initFunction === "function") { await initFunction(app); - app.logger.info(`Loaded plugin ${pluginDisplay}`); + app.logger.info(app.i18n.loaded_plugin_0(pluginDisplay)); } else { app.logger.error( - `Invalid structure in plugin ${pluginDisplay}, no load function found.`, + app.i18n.invalid_plugin_0_missing_load_function( + pluginDisplay, + ), ); } } catch (error) { app.logger.error( - `The plugin ${pluginDisplay} could not be loaded.`, + app.i18n.plugin_0_could_not_be_loaded(pluginDisplay), ); if (error instanceof Error && error.stack) { - app.logger.error(error.stack); + app.logger.error(error.stack as TranslatedString); } } } diff --git a/src/lib/utils/reflections.ts b/src/lib/utils/reflections.ts index c215ff001..d8f367d3f 100644 --- a/src/lib/utils/reflections.ts +++ b/src/lib/utils/reflections.ts @@ -3,8 +3,8 @@ import { DeclarationReflection, makeRecursiveVisitor, ParameterReflection, - ProjectReflection, - ReferenceType, + type ProjectReflection, + type ReferenceType, Reflection, SignatureReflection, TypeParameterReflection, @@ -46,7 +46,7 @@ export function discoverAllReferenceTypes( current.type?.visit(visitor); add(current.typeParameters); add(current.signatures); - add(current.indexSignature); + add(current.indexSignatures); add(current.getSignature); add(current.setSignature); current.overwrites?.visit(visitor); diff --git a/src/lib/utils/set.ts b/src/lib/utils/set.ts new file mode 100644 index 000000000..2c9692d17 --- /dev/null +++ b/src/lib/utils/set.ts @@ -0,0 +1,17 @@ +export function setIntersection<T>(a: Iterable<T>, b: Set<T>): Set<T> { + const result = new Set<T>(); + for (const elem of a) { + if (b.has(elem)) { + result.add(elem); + } + } + return result; +} + +export function setDifference<T>(a: Iterable<T>, b: Iterable<T>): Set<T> { + const result = new Set(a); + for (const elem of b) { + result.delete(elem); + } + return result; +} diff --git a/src/lib/utils/sort.ts b/src/lib/utils/sort.ts index 476fb65fd..4770d40ff 100644 --- a/src/lib/utils/sort.ts +++ b/src/lib/utils/sort.ts @@ -5,12 +5,13 @@ import { ReflectionKind } from "../models/reflections/kind"; import type { DeclarationReflection } from "../models/reflections/declaration"; -import { LiteralType } from "../models/types"; import type { Options } from "./options"; +import type { DocumentReflection } from "../models"; export const SORT_STRATEGIES = [ "source-order", "alphabetical", + "alphabetical-ignoring-documents", "enum-value-ascending", "enum-value-descending", "enum-member-source-order", @@ -20,11 +21,14 @@ export const SORT_STRATEGIES = [ "required-first", "kind", "external-last", + "documents-first", + "documents-last", ] as const; export type SortStrategy = (typeof SORT_STRATEGIES)[number]; const defaultKindSortOrder = [ + ReflectionKind.Document, ReflectionKind.Reference, ReflectionKind.Project, ReflectionKind.Module, @@ -56,8 +60,8 @@ const defaultKindSortOrder = [ const sorts: Record< SortStrategy, ( - a: DeclarationReflection, - b: DeclarationReflection, + a: DeclarationReflection | DocumentReflection, + b: DeclarationReflection | DocumentReflection, data: { kindSortOrder: ReflectionKind[] }, ) => boolean > = { @@ -88,15 +92,27 @@ const sorts: Record< alphabetical(a, b) { return a.name < b.name; }, + "alphabetical-ignoring-documents"(a, b) { + if ( + a.kindOf(ReflectionKind.Document) || + b.kindOf(ReflectionKind.Document) + ) { + return false; + } + return a.name < b.name; + }, "enum-value-ascending"(a, b) { if ( a.kind == ReflectionKind.EnumMember && b.kind == ReflectionKind.EnumMember ) { + const aRefl = a as DeclarationReflection; + const bRefl = b as DeclarationReflection; + const aValue = - a.type instanceof LiteralType ? a.type.value : -Infinity; + aRefl.type?.type === "literal" ? aRefl.type.value : -Infinity; const bValue = - b.type instanceof LiteralType ? b.type.value : -Infinity; + bRefl.type?.type === "literal" ? bRefl.type.value : -Infinity; return aValue! < bValue!; } @@ -107,10 +123,13 @@ const sorts: Record< a.kind == ReflectionKind.EnumMember && b.kind == ReflectionKind.EnumMember ) { + const aRefl = a as DeclarationReflection; + const bRefl = b as DeclarationReflection; + const aValue = - a.type instanceof LiteralType ? a.type.value : -Infinity; + aRefl.type?.type === "literal" ? aRefl.type.value : -Infinity; const bValue = - b.type instanceof LiteralType ? b.type.value : -Infinity; + bRefl.type?.type === "literal" ? bRefl.type.value : -Infinity; return bValue! < aValue!; } @@ -155,6 +174,18 @@ const sorts: Record< "external-last"(a, b) { return !a.flags.isExternal && b.flags.isExternal; }, + "documents-first"(a, b) { + return ( + a.kindOf(ReflectionKind.Document) && + !b.kindOf(ReflectionKind.Document) + ); + }, + "documents-last"(a, b) { + return ( + !a.kindOf(ReflectionKind.Document) && + b.kindOf(ReflectionKind.Document) + ); + }, }; export function getSortFunction(opts: Options) { @@ -171,7 +202,9 @@ export function getSortFunction(opts: Options) { const strategies = opts.getValue("sort"); const data = { kindSortOrder }; - return function sortReflections(reflections: DeclarationReflection[]) { + return function sortReflections( + reflections: (DeclarationReflection | DocumentReflection)[], + ) { reflections.sort((a, b) => { for (const s of strategies) { if (sorts[s](a, b, data)) { diff --git a/src/lib/utils/tsconfig.ts b/src/lib/utils/tsconfig.ts index f25bd41bf..76e982861 100644 --- a/src/lib/utils/tsconfig.ts +++ b/src/lib/utils/tsconfig.ts @@ -55,7 +55,7 @@ export function getTypeDocOptionsFromTsConfig(file: string): any { return result; } -const tsConfigCache: Record<string, ts.ParsedCommandLine> = {}; +const tsConfigCache: Record<string, ts.ParsedCommandLine | undefined> = {}; export function readTsConfig( path: string, diff --git a/src/lib/utils/validation.ts b/src/lib/utils/validation.ts index 2c6cce782..b2aea68c0 100644 --- a/src/lib/utils/validation.ts +++ b/src/lib/utils/validation.ts @@ -1,27 +1,28 @@ -export type Infer<T extends Schema> = T extends Optional<infer U> - ? Infer<U> - : T extends Guard<infer U> - ? U - : T extends typeof String - ? string - : T extends typeof Number - ? number - : T extends typeof Boolean - ? boolean - : T extends readonly string[] - ? T[number] - : T extends readonly [typeof Array, Schema] - ? Array<Infer<T[1]>> - : { - -readonly [K in OptionalKeys<T>]?: Infer< - Extract<T[K & keyof T], Schema> - >; - } & { - -readonly [K in Exclude< - keyof T, - OptionalKeys<T> | typeof additionalProperties - >]: Infer<Extract<T[K], Schema>>; - }; +export type Infer<T extends Schema> = + T extends Optional<infer U> + ? Infer<U> + : T extends Guard<infer U> + ? U + : T extends typeof String + ? string + : T extends typeof Number + ? number + : T extends typeof Boolean + ? boolean + : T extends readonly string[] + ? T[number] + : T extends readonly [typeof Array, Schema] + ? Array<Infer<T[1]>> + : { + -readonly [K in OptionalKeys<T>]?: Infer< + Extract<T[K & keyof T], Schema> + >; + } & { + -readonly [K in Exclude< + keyof T, + OptionalKeys<T> | typeof additionalProperties + >]: Infer<Extract<T[K], Schema>>; + }; export type Optional<T extends Schema> = Record<typeof opt, T>; export type Guard<T> = (x: unknown) => x is T; diff --git a/src/lib/validation/documentation.ts b/src/lib/validation/documentation.ts index 29e7fcdda..da9df9db4 100644 --- a/src/lib/validation/documentation.ts +++ b/src/lib/validation/documentation.ts @@ -1,7 +1,7 @@ import { DeclarationReflection, - ProjectReflection, - Reflection, + type ProjectReflection, + type Reflection, ReflectionKind, ReflectionType, } from "../models"; @@ -43,7 +43,7 @@ export function validateDocumentation( if (seen.has(ref)) continue; seen.add(ref); - // If we're a non-parameter inside a parameter, we shouldn't care. Parameters don't get deeply documented + // If inside a parameter, we shouldn't care. Callback parameter's values don't get deeply documented. let r: Reflection | undefined = ref.parent; while (r) { if (r.kindOf(ReflectionKind.Parameter)) { @@ -71,6 +71,15 @@ export function validateDocumentation( continue; } + // Construct signatures are considered documented if they are directly within a documented type alias. + if ( + ref.kindOf(ReflectionKind.ConstructorSignature) && + ref.parent?.parent?.kindOf(ReflectionKind.TypeAlias) + ) { + toProcess.push(ref.parent.parent); + continue; + } + if (ref instanceof DeclarationReflection) { const signatures = ref.type instanceof ReflectionType @@ -97,11 +106,11 @@ export function validateDocumentation( } logger.warn( - `${ref.getFriendlyFullName()} (${ - ReflectionKind[ref.kind] - }), defined in ${nicePath( - symbolId.fileName, - )}, does not have any documentation.`, + logger.i18n.reflection_0_kind_1_defined_in_2_does_not_have_any_documentation( + ref.getFriendlyFullName(), + ReflectionKind[ref.kind], + nicePath(symbolId.fileName), + ), ); } } diff --git a/src/lib/validation/exports.ts b/src/lib/validation/exports.ts index 236ae6f01..b439f0db3 100644 --- a/src/lib/validation/exports.ts +++ b/src/lib/validation/exports.ts @@ -85,9 +85,11 @@ export function validateExports( warned.add(uniqueId!); logger.warn( - `${type.qualifiedName}, defined in ${nicePath( - type.symbolId!.fileName, - )}, is referenced by ${owner.getFullName()} but not included in the documentation.`, + logger.i18n.type_0_defined_in_1_is_referenced_by_2_but_not_included_in_docs( + type.qualifiedName, + nicePath(type.symbolId!.fileName), + owner.getFriendlyFullName(), + ), ); } } @@ -95,8 +97,9 @@ export function validateExports( const unusedIntentional = intentional.getUnused(); if (unusedIntentional.length) { logger.warn( - "The following symbols were marked as intentionally not exported, but were either not referenced in the documentation, or were exported:\n\t" + + logger.i18n.invalid_intentionally_not_exported_symbols_0( unusedIntentional.join("\n\t"), + ), ); } } diff --git a/src/lib/validation/links.ts b/src/lib/validation/links.ts index ca2fb39d4..3a2c103d3 100644 --- a/src/lib/validation/links.ts +++ b/src/lib/validation/links.ts @@ -1,12 +1,17 @@ -import type { Comment, CommentDisplayPart, ProjectReflection } from "../models"; +import { + ReflectionKind, + type Comment, + type CommentDisplayPart, + type ProjectReflection, +} from "../models"; import type { Logger } from "../utils"; const linkTags = ["@link", "@linkcode", "@linkplain"]; -function getBrokenLinks(comment: Comment | undefined) { +function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) { const links: string[] = []; - function processPart(part: CommentDisplayPart) { + for (const part of parts) { if ( part.kind === "inline-tag" && linkTags.includes(part.tag) && @@ -16,8 +21,16 @@ function getBrokenLinks(comment: Comment | undefined) { } } - comment?.summary.forEach(processPart); - comment?.blockTags.forEach((tag) => tag.content.forEach(processPart)); + return links; +} + +function getBrokenLinks(comment: Comment | undefined) { + if (!comment) return []; + + const links = [...getBrokenPartLinks(comment.summary)]; + for (const tag of comment.blockTags) { + links.push(...getBrokenPartLinks(tag.content)); + } return links; } @@ -28,20 +41,79 @@ export function validateLinks( ): void { for (const id in project.reflections) { const reflection = project.reflections[id]; + + if (reflection.isProject() || reflection.isDeclaration()) { + for (const broken of getBrokenPartLinks(reflection.readme || [])) { + // #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( + logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1_may_have_meant_2( + broken, + reflection.getFriendlyFullName(), + broken.replace(/[.#~]/, "!"), + ), + ); + } else { + logger.warn( + logger.i18n.failed_to_resolve_link_to_0_in_readme_for_1( + broken, + reflection.getFriendlyFullName(), + ), + ); + } + } + } + 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. - let extra = ""; if (broken.startsWith("@") && !broken.includes("!")) { - extra = `\n\tYou may have wanted "${broken.replace( - /[.#~]/, - "!", - )}"`; + logger.warn( + logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( + broken, + reflection.getFriendlyFullName(), + broken.replace(/[.#~]/, "!"), + ), + ); + } else { + logger.warn( + logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1( + broken, + reflection.getFriendlyFullName(), + ), + ); + } + } + + if ( + reflection.isDeclaration() && + reflection.kindOf(ReflectionKind.TypeAlias) && + reflection.type?.type === "union" && + reflection.type.elementSummaries + ) { + for (const broken of reflection.type.elementSummaries.flatMap( + getBrokenPartLinks, + )) { + if (broken.startsWith("@") && !broken.includes("!")) { + logger.warn( + logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( + broken, + reflection.getFriendlyFullName(), + broken.replace(/[.#~]/, "!"), + ), + ); + } else { + logger.warn( + logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1( + broken, + reflection.getFriendlyFullName(), + ), + ); + } } - logger.warn( - `Failed to resolve link to "${broken}" in comment for ${reflection.getFriendlyFullName()}.${extra}`, - ); } } } diff --git a/src/test/Repository.test.ts b/src/test/Repository.test.ts index e62d95383..c1efd8441 100644 --- a/src/test/Repository.test.ts +++ b/src/test/Repository.test.ts @@ -1,14 +1,31 @@ import { GitRepository, guessSourceUrlTemplate, + RepositoryManager, } from "../lib/converter/utils/repository"; -import { strictEqual as equal, ok } from "assert"; -import { tempdirProject } from "@typestrong/fs-fixture-builder"; +import { deepStrictEqual as equal, ok } from "assert"; +import { type Project, tempdirProject } from "@typestrong/fs-fixture-builder"; import { spawnSync } from "child_process"; import { TestLogger } from "./TestLogger"; import { join } from "path"; import { normalizePath } from "../lib/utils/paths"; +function git(cwd: string, ...args: string[]) { + const env = { + GIT_AUTHOR_NAME: "test", + GIT_AUTHOR_EMAIL: "test@example.com", + GIT_AUTHOR_DATE: "2024-03-31T22:04:50.119Z", + GIT_COMMITTER_NAME: "test", + GIT_COMMITTER_EMAIL: "test@example.com", + GIT_COMMITTER_DATE: "2024-03-31T22:04:50.119Z", + }; + return spawnSync("git", ["-C", cwd, ...args], { + encoding: "utf-8", + windowsHide: true, + env, + }); +} + describe("Repository", function () { describe("guessSourceUrlTemplate helper", () => { it("handles a personal GitHub HTTPS URL", () => { @@ -109,23 +126,7 @@ describe("Repository", function () { }); describe("getURL", () => { - const project = tempdirProject(); - function git(...args: string[]) { - const env = { - GIT_AUTHOR_NAME: "test", - GIT_AUTHOR_EMAIL: "test@example.com", - GIT_AUTHOR_DATE: "2024-03-31T22:04:50.119Z", - GIT_COMMITTER_NAME: "test", - GIT_COMMITTER_EMAIL: "test@example.com", - GIT_COMMITTER_DATE: "2024-03-31T22:04:50.119Z", - }; - return spawnSync("git", ["-C", project.cwd, ...args], { - encoding: "utf-8", - windowsHide: true, - env, - }); - } - + using project = tempdirProject(); afterEach(() => { project.rm(); }); @@ -134,10 +135,11 @@ describe("Repository", function () { project.addFile("test.js", "console.log('hi!')"); project.write(); - git("init", "-b", "test"); - git("add", "."); - git("commit", "-m", "Test commit"); + git(project.cwd, "init", "-b", "test"); + git(project.cwd, "add", "."); + git(project.cwd, "commit", "-m", "Test commit"); git( + project.cwd, "remote", "add", "origin", @@ -160,3 +162,157 @@ describe("Repository", function () { }); }); }); + +describe("RepositoryManager - no git", () => {}); + +describe("RepositoryManager - git enabled", () => { + let fix: Project; + const logger = new TestLogger(); + const manager = new RepositoryManager( + "", + "revision", + "remote", + "link:{path}", + false, // disable git + logger, + ); + + before(function () { + function createRepo(path: string) { + git(path, "init", "-b", "test"); + git(path, "add", "."); + git(path, "commit", "-m", "Test commit"); + } + + fix = tempdirProject(); + fix.addFile("root.txt"); + fix.addFile(".gitignore", "/ignored"); + fix.addSymlink("self", "."); + fix.addSymlink("sub", "subfolder"); + fix.dir("subfolder", (dir) => { + dir.addFile("sub.txt"); + }); + fix.dir("ignored", (dir) => { + dir.addFile("ignored.txt"); + }); + fix.dir("sub_repo", (dir) => { + dir.addFile("repo.txt"); + }); + + try { + fix.write(); + } catch (error) { + if (process.platform === "win32") { + // Don't have permission to create symlinks + return this.skip(); + } + throw error; + } + createRepo(join(fix.cwd, "sub_repo")); + createRepo(fix.cwd); + }); + after(() => { + fix.rm(); + }); + + afterEach(() => { + logger.expectNoOtherMessages(); + logger.reset(); + }); + + it("Handles the simplest case", () => { + const root = normalizePath(join(fix.cwd, "root.txt")); + const repo = manager.getRepository(root) as GitRepository; + ok(repo); + equal(repo.getURL(root, 1), "link:root.txt"); + equal( + repo.files, + new Set( + [ + ".gitignore", + "root.txt", + "self", + "sub", + "sub_repo", + "subfolder/sub.txt", + ].map((f) => normalizePath(join(fix.cwd, f))), + ), + ); + }); + + it("Handles a recursive self-symlink", () => { + const root = join(fix.cwd, "self/self/self/root.txt"); + const repo = manager.getRepository(root) as GitRepository; + ok(repo); + // Ideally, this would probably be link:root.txt, but I'll + // settle for not crashing right now. + equal(repo.getURL(root, 1), undefined); + equal( + repo.files, + new Set( + [ + ".gitignore", + "root.txt", + "self", + "sub", + "sub_repo", + "subfolder/sub.txt", + ].map((f) => normalizePath(join(fix.cwd, f))), + ), + ); + }); + + it("Handles a nested repository", () => { + const sub = normalizePath(join(fix.cwd, "sub_repo/repo.txt")); + const repo = manager.getRepository(sub) as GitRepository; + ok(repo); + equal(repo.path, normalizePath(join(fix.cwd, "sub_repo"))); + equal(repo.getURL(sub, 1), "link:repo.txt"); + equal(repo.files.size, 1); + }); + + it("Caches repositories", () => { + // Load cache + for (const path of [ + "root.txt", + "self/self/self/root.txt", + "sub_repo/repo.txt", + "ignored/ignored.txt", + "subfolder/sub.txt", + ]) { + manager.getRepository(join(fix.cwd, path)); + } + + const root = join(fix.cwd, "root.txt"); + const rootIndirect = join(fix.cwd, "self/self/self/root.txt"); + const subfolder = join(fix.cwd, "subfolder/sub.txt"); + const repo = manager.getRepository(root) as GitRepository; + const repo2 = manager.getRepository(rootIndirect) as GitRepository; + const repo3 = manager.getRepository(subfolder) as GitRepository; + ok(repo === repo2); + ok(repo === repo3); + + const sub = join(fix.cwd, "sub_repo/repo.txt"); + const subRepo = manager.getRepository(sub) as GitRepository; + const subRepo2 = manager.getRepository(sub) as GitRepository; + ok(subRepo === subRepo2); + + equal( + manager["cache"], + new Map([ + [normalizePath(fix.cwd), repo], + [normalizePath(join(fix.cwd, "self/self/self")), repo], + [normalizePath(join(fix.cwd, "sub_repo")), subRepo], + [normalizePath(join(fix.cwd, "ignored")), repo], + [normalizePath(join(fix.cwd, "subfolder")), repo], + ]), + ); + }); + + it("Handles .gitignored paths", () => { + const ign = join(fix.cwd, "ignored/ignored.txt"); + const repo = manager.getRepository(ign); + equal(repo?.path, normalizePath(fix.cwd)); + equal(repo.getURL(ign, 1), undefined); + }); +}); diff --git a/src/test/TestLogger.ts b/src/test/TestLogger.ts index b1d63a0c8..cde695170 100644 --- a/src/test/TestLogger.ts +++ b/src/test/TestLogger.ts @@ -2,6 +2,10 @@ import { Logger, LogLevel } from "../lib/utils"; import { fail, ok } from "assert"; import ts from "typescript"; import { resolve } from "path"; +import { + Internationalization, + type TranslationProxy, +} from "../lib/internationalization/internationalization"; const levelMap: Record<LogLevel, string> = { [LogLevel.None]: "none: ", @@ -13,6 +17,7 @@ const levelMap: Record<LogLevel, string> = { export class TestLogger extends Logger { messages: string[] = []; + override i18n: TranslationProxy = new Internationalization(null).proxy; reset() { this.resetErrors(); diff --git a/src/test/behavior.c2.test.ts b/src/test/behavior.c2.test.ts index ff1f5c970..82a875c86 100644 --- a/src/test/behavior.c2.test.ts +++ b/src/test/behavior.c2.test.ts @@ -6,7 +6,7 @@ import { CommentTag, Reflection, SignatureReflection, - ContainerReflection, + type ContainerReflection, } from "../lib/models"; import { filterMap } from "../lib/utils"; import { CommentStyle } from "../lib/utils/options/declaration"; @@ -21,7 +21,7 @@ import { existsSync } from "fs"; import { clearCommentCache } from "../lib/converter/comments"; import { getComment, query, querySig } from "./utils"; -type NameTree = { [name: string]: NameTree }; +type NameTree = { [name: string]: NameTree | undefined }; function buildNameTree( refl: ContainerReflection, @@ -49,7 +49,7 @@ function getLinks(refl: Reflection) { ]; } if (p.target instanceof Reflection) { - return [p.target?.kind, p.target?.getFullName()]; + return [p.target.kind, p.target.getFullName()]; } return [p.target?.qualifiedName]; } @@ -272,11 +272,10 @@ describe("Behavior Tests", () => { ["Variable class", "Stat docs", "Inst docs"], ); - equal(project.children?.map((c) => c.name), [ - "BadClass", - "CallableClass", - "VariableClass", - ]); + equal( + project.children?.map((c) => c.name), + ["BadClass", "CallableClass", "VariableClass"], + ); }); it("Handles const type parameters", () => { @@ -289,7 +288,10 @@ describe("Behavior Tests", () => { it("Handles declare global 'modules'", () => { const project = convert("declareGlobal"); - equal(project.children?.map((c) => c.name), ["DeclareGlobal"]); + equal( + project.children?.map((c) => c.name), + ["DeclareGlobal"], + ); }); it("Handles duplicate heritage clauses", () => { @@ -384,7 +386,7 @@ describe("Behavior Tests", () => { ]); logger.expectMessage( - "warn: The first line of an example tag will be taken literally as the example name, and should only contain text.", + "warn: The first line of an example tag will be taken literally as the example name, and should only contain text", ); logger.expectNoOtherMessages(); }); @@ -429,7 +431,7 @@ describe("Behavior Tests", () => { ); logger.expectMessage("warn: Unmatched closing brace"); logger.expectMessage( - "warn: The first line of an example tag will be taken literally as the example name, and should only contain text.", + "warn: The first line of an example tag will be taken literally as the example name, and should only contain text", ); logger.expectNoOtherMessages(); }); @@ -438,7 +440,10 @@ describe("Behavior Tests", () => { app.options.setValue("excludeCategories", ["A", "Default"]); app.options.setValue("defaultCategory", "Default"); const project = convert("excludeCategories"); - equal(project.children?.map((c) => c.name), ["c"]); + equal( + project.children?.map((c) => c.name), + ["c"], + ); }); it("Handles excludeNotDocumentedKinds", () => { @@ -478,14 +483,15 @@ describe("Behavior Tests", () => { typescript: { Promise: "/promise2", }, - "@types/marked": { - Lexer: "https://marked.js.org/using_pro#lexer", - "*": "https://marked.js.org", + "@types/markdown-it": { + "MarkdownIt.Token": + "https://markdown-it.github.io/markdown-it/#Token", + "*": "https://markdown-it.github.io/markdown-it/", }, }); const project = convert("externalSymbols"); const p = query(project, "P"); - equal(p.comment?.summary?.[1], { + equal(p.comment?.summary[1], { kind: "inline-tag", tag: "@link", target: "/promise", @@ -495,13 +501,16 @@ describe("Behavior Tests", () => { equal(p.type?.type, "reference" as const); equal(p.type.externalUrl, "/promise2"); - const m = query(project, "L"); + const m = query(project, "T"); equal(m.type?.type, "reference" as const); - equal(m.type.externalUrl, "https://marked.js.org/using_pro#lexer"); + equal( + m.type.externalUrl, + "https://markdown-it.github.io/markdown-it/#Token", + ); - const s = query(project, "S"); + const s = query(project, "Pr"); equal(s.type?.type, "reference" as const); - equal(s.type.externalUrl, "https://marked.js.org"); + equal(s.type.externalUrl, "https://markdown-it.github.io/markdown-it/"); }); it("Handles @group tag", () => { @@ -511,15 +520,13 @@ describe("Behavior Tests", () => { const C = query(project, "C"); const D = query(project, "D"); - equal(project.groups?.map((g) => g.title), [ - "Variables", - "A", - "B", - "With Spaces", - ]); + equal( + project.groups?.map((g) => g.title), + ["Variables", "A", "B", "With Spaces"], + ); equal( - project.groups?.map((g) => + project.groups.map((g) => Comment.combineDisplayParts(g.description), ), ["Variables desc", "A description", "", "With spaces desc"], @@ -534,7 +541,10 @@ describe("Behavior Tests", () => { it("Inherits @group tag if comment is not redefined", () => { const project = convert("groupInheritance"); const cls = query(project, "Cls"); - equal(cls.groups?.map((g) => g.title), ["Constructors", "Group"]); + equal( + cls.groups?.map((g) => g.title), + ["Constructors", "Group"], + ); equal( cls.groups.map((g) => g.children), [[query(project, "Cls.constructor")], [query(project, "Cls.prop")]], @@ -545,9 +555,12 @@ describe("Behavior Tests", () => { app.options.setValue("categorizeByGroup", false); const project = convert("categoryInheritance"); const cls = query(project, "Cls"); - equal(cls.categories?.map((g) => g.title), ["Cat", "Other"]); equal( - cls.categories?.map((g) => + cls.categories?.map((g) => g.title), + ["Cat", "Other"], + ); + equal( + cls.categories.map((g) => Comment.combineDisplayParts(g.description), ), ["Cat desc", ""], @@ -561,12 +574,18 @@ describe("Behavior Tests", () => { it("Handles hidden accessors", () => { const project = convert("hiddenAccessor"); const test = query(project, "Test"); - equal(test.children?.map((c) => c.name), [ - "constructor", - "auto", - "x", - "y", - ]); + equal( + test.children?.map((c) => c.name), + ["constructor", "auto", "x", "y"], + ); + }); + + it("Handles @hideconstructor", () => { + const project = convert("hideconstructor"); + + ok(!project.getChildByName("StaticOnly.constructor")); + ok(!!project.getChildByName("StaticOnly.notHidden")); + ok(!project.getChildByName("IgnoredCtor.constructor")); }); it("Handles simple @inheritDoc cases", () => { @@ -708,11 +727,11 @@ describe("Behavior Tests", () => { const target4 = query(project, "target4"); ok(target4.comment?.getTag("@inheritDoc")); logger.expectMessage( - "warn: target4 tried to copy a comment from source2 with @inheritDoc, but the source has no associated comment.", + "warn: target4 tried to copy a comment from source2 with @inheritDoc, but the source has no associated comment", ); logger.expectMessage( - "warn: Declaration reference in @inheritDoc for badParse was not fully parsed and may resolve incorrectly.", + "warn: Declaration reference in @inheritDoc for badParse was not fully parsed and may resolve incorrectly", ); logger.expectNoOtherMessages(); @@ -900,7 +919,7 @@ describe("Behavior Tests", () => { equal(Comment.combineDisplayParts(b.comment?.summary), "Comment 1"); logger.expectMessage( - "warn: MultiCommentMultiDeclaration has multiple declarations with a comment. An arbitrary comment will be used.", + "warn: MultiCommentMultiDeclaration has multiple declarations with a comment. An arbitrary comment will be used", ); logger.expectMessage( "info: The comments for MultiCommentMultiDeclaration are declared at*", @@ -937,20 +956,23 @@ describe("Behavior Tests", () => { equal(fooComments, ["No arg comment\n", "No arg comment\n"]); equal(foo.comment, undefined); - equal(foo.signatures?.map((s) => s.comment?.label), [ - "NO_ARGS", - "WITH_X", - ]); + equal( + foo.signatures?.map((s) => s.comment?.label), + ["NO_ARGS", "WITH_X"], + ); const bar = query(project, "bar"); const barComments = bar.signatures?.map((sig) => Comment.combineDisplayParts(sig.comment?.summary), ); - equal(barComments, ["Implementation comment", "Custom comment"]); - equal(bar.comment, undefined); + equal(barComments, ["", "Custom comment"]); + equal( + Comment.combineDisplayParts(bar.comment?.summary), + "Implementation comment", + ); logger.expectMessage( - 'warn: The label "bad" for badLabel cannot be referenced with a declaration reference. Labels may only contain A-Z, 0-9, and _, and may not start with a number.', + 'warn: The label "bad" for badLabel cannot be referenced with a declaration reference. Labels may only contain A-Z, 0-9, and _, and may not start with a number', ); logger.expectNoOtherMessages(); }); @@ -1058,7 +1080,10 @@ describe("Behavior Tests", () => { const project = convert("typeAliasInterface"); const bar = query(project, "Bar"); equal(bar.kind, ReflectionKind.Interface); - equal(bar.children?.map((c) => c.name), ["a", "b"]); + equal( + bar.children?.map((c) => c.name), + ["a", "b"], + ); const comments = [bar, bar.children[0], bar.children[1]].map((r) => Comment.combineDisplayParts(r.comment?.summary), @@ -1070,34 +1095,35 @@ describe("Behavior Tests", () => { it("Allows specifying group sort order #2251", () => { app.options.setValue("groupOrder", ["B", "Variables", "A"]); const project = convert("groupTag"); - equal(project.groups?.map((g) => g.title), [ - "B", - "Variables", - "A", - "With Spaces", - ]); + equal( + project.groups?.map((g) => g.title), + ["B", "Variables", "A", "With Spaces"], + ); }); it("Supports disabling sorting of entry points #2393", () => { app.options.setValue("sort", ["alphabetical"]); const project = convert("blockComment", "asConstEnum"); - equal(project.children?.map((c) => c.name), [ - "asConstEnum", - "blockComment", - ]); + equal( + project.children?.map((c) => c.name), + ["asConstEnum", "blockComment"], + ); app.options.setValue("sortEntryPoints", false); const project2 = convert("blockComment", "asConstEnum"); - equal(project2.children?.map((c) => c.name), [ - "blockComment", - "asConstEnum", - ]); + equal( + project2.children?.map((c) => c.name), + ["blockComment", "asConstEnum"], + ); }); it("Respects resolution-mode when resolving types", () => { app.options.setValue("excludeExternals", false); const MergedType = query(convert("resolutionMode"), "MergedType"); - equal(MergedType.children?.map((child) => child.name), ["cjs", "esm"]); + equal( + MergedType.children?.map((child) => child.name), + ["cjs", "esm"], + ); }); it("Special cases some `this` type occurrences", () => { @@ -1144,15 +1170,15 @@ describe("Behavior Tests", () => { ); 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.', + '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); @@ -1179,4 +1205,32 @@ describe("Behavior Tests", () => { equal(sig.parameters[0].type?.toString(), "C[]"); equal(sig.parameters[1].type?.toString(), "NoInfer<C>"); }); + + it("Handles inferred predicate functions from TS 5.5", () => { + const project = convert("inferredPredicates"); + const sig = querySig(project, "isNumber"); + equal(sig.type?.toString(), "x is number"); + const sig2 = querySig(project, "isNonNullish"); + equal(sig2.type?.toString(), "x is NonNullable<T>"); + }); + + it("Cascades specified modifier tags to child reflections, #2056", () => { + const project = convert("cascadedModifiers"); + + const mods = (s: string) => query(project, s).comment?.modifierTags; + const sigMods = (s: string) => + querySig(project, s).comment?.modifierTags; + + equal(mods("BetaStuff"), new Set(["@beta"])); + equal(mods("BetaStuff.AlsoBeta"), new Set(["@beta"])); + equal(mods("BetaStuff.AlsoBeta.betaFish"), new Set()); + equal(mods("BetaStuff.AlsoBeta.alphaFish"), new Set()); + + equal(sigMods("BetaStuff.AlsoBeta.betaFish"), new Set(["@beta"])); + equal(sigMods("BetaStuff.AlsoBeta.alphaFish"), new Set(["@alpha"])); + + logger.expectMessage( + "warn: The modifier tag @alpha is mutually exclusive with @beta in the comment for mutuallyExclusive", + ); + }); }); diff --git a/src/test/capture-screenshots.ts b/src/test/capture-screenshots.ts deleted file mode 100644 index e46b4e0b8..000000000 --- a/src/test/capture-screenshots.ts +++ /dev/null @@ -1,130 +0,0 @@ -import * as fs from "fs"; -import { platform } from "os"; -import { resolve, join, dirname, relative } from "path"; -import { Application, EntryPointStrategy } from ".."; -import { glob } from "../lib/utils/fs"; - -// The @types package plays badly with non-dom packages. -// eslint-disable-next-line @typescript-eslint/no-var-requires -const puppeteer = require("puppeteer"); - -const concurrency = 10; -const src = join(__dirname, "../../src/test/renderer/testProject/src"); -const baseDirectory = join(__dirname, "../../tmp/capture"); -const outputDirectory = join(__dirname, "../../tmp/screenshots"); -const viewport = { width: 1024, height: 768 }; - -class PQueue { - private queued: (() => Promise<void>)[] = []; - constructor(private concurrency: number) {} - - add(action: () => Promise<void>) { - this.queued.push(action); - } - - run() { - return new Promise<void>((resolve, reject) => { - const queue: Promise<void>[] = []; - const doReject = (err: unknown) => { - this.queued.length = 0; - queue.length = 0; - reject(err); - }; - const tick = () => { - while (queue.length < this.concurrency) { - const next = this.queued.shift(); - if (next) { - const nextPromise = Promise.resolve().then(next); - queue.push(nextPromise); - nextPromise.then(() => { - void queue.splice(queue.indexOf(nextPromise), 1); - tick(); - }, doReject); - } else { - break; - } - } - - if (queue.length === 0) { - resolve(); - } - }; - - tick(); - }); - } -} - -export async function captureRegressionScreenshots() { - const app = await Application.bootstrap({ - readme: join(src, "..", "README.md"), - name: "typedoc", - cleanOutputDir: true, - tsconfig: join(src, "..", "tsconfig.json"), - plugin: [], - entryPoints: [src], - entryPointStrategy: EntryPointStrategy.Expand, - }); - const project = await app.convert(); - if (!project) throw new Error("Failed to convert."); - await fs.promises.rm(outputDirectory, { recursive: true, force: true }); - await app.generateDocs(project, baseDirectory); - - await captureScreenshots(baseDirectory, outputDirectory); -} - -export async function captureScreenshots( - baseDirectory: string, - outputDirectory: string, -) { - const browser = await puppeteer.launch({ - args: - platform() === "win32" - ? [] - : ["--no-sandbox", "--disable-setuid-sandbox"], - }); - - const queue = new PQueue(concurrency); - for (const file of glob("**/*.html", baseDirectory)) { - queue.add(async () => { - const absPath = resolve(baseDirectory, file); - const outputPath = resolve( - outputDirectory, - relative(baseDirectory, file).replace(".html", ""), - ); - fs.mkdirSync(dirname(outputPath), { recursive: true }); - - const page = await browser.newPage(); - await page.setViewport(viewport); - await page.goto(`file://${absPath}`, { - waitUntil: "domcontentloaded", // 'load' 'networkidle0' 'networkidle2' - }); - await new Promise<void>((res) => setTimeout(() => res(), 100)); - await page.screenshot({ - path: outputPath + "-light.png", - fullPage: true, - }); - - await page.evaluate('document.body.classList.add("dark")'); - await new Promise<void>((res) => setTimeout(() => res(), 100)); - - await page.screenshot({ - path: outputPath + "-dark.png", - fullPage: true, - }); - - await page.close(); - }); - } - await queue.run(); - - await browser.close(); -} - -if (require.main == module) { - captureRegressionScreenshots().catch((err) => { - // eslint-disable-next-line no-console - console.error(err); - process.exit(1); - }); -} diff --git a/src/test/comments.test.ts b/src/test/comments.test.ts index e05a64d23..e15ce775f 100644 --- a/src/test/comments.test.ts +++ b/src/test/comments.test.ts @@ -4,13 +4,14 @@ import type { CommentParserConfig } from "../lib/converter/comments"; import { lexBlockComment } from "../lib/converter/comments/blockLexer"; import { lexLineComments } from "../lib/converter/comments/lineLexer"; -import { Token, TokenSyntaxKind } from "../lib/converter/comments/lexer"; +import { type Token, TokenSyntaxKind } from "../lib/converter/comments/lexer"; import { parseComment } from "../lib/converter/comments/parser"; import { lexCommentString } from "../lib/converter/comments/rawLexer"; -import { Comment, CommentTag } from "../lib/models"; +import { Comment, type CommentDisplayPart, CommentTag } from "../lib/models"; import { MinimalSourceFile } from "../lib/utils/minimalSourceFile"; import { TestLogger } from "./TestLogger"; import { extractTagName } from "../lib/converter/comments/tagName"; +import { FileRegistry } from "../lib/models/FileRegistry"; function dedent(text: string) { const lines = text.split(/\r?\n/); @@ -927,9 +928,7 @@ describe("Raw Lexer", () => { const tokens = lex(" Comment\nNext line "); equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "Comment", pos: 1 }, - { kind: TokenSyntaxKind.NewLine, text: "\n", pos: 8 }, - { kind: TokenSyntaxKind.Text, text: "Next line", pos: 9 }, + { kind: TokenSyntaxKind.Text, text: "Comment\nNext line", pos: 1 }, ]); }); @@ -959,54 +958,22 @@ describe("Raw Lexer", () => { ]); }); - it("Should recognize tags", () => { - const tokens = lex("@tag @a @abc234"); - - equal(tokens, [ - { kind: TokenSyntaxKind.Tag, text: "@tag", pos: 0 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 4 }, - { kind: TokenSyntaxKind.Tag, text: "@a", pos: 5 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 7 }, - { kind: TokenSyntaxKind.Tag, text: "@abc234", pos: 8 }, - ]); - }); + it("Should not recognize tags", () => { + const tokens = lex("@123 @@ @ @tag @a @abc234"); - it("Should not indiscriminately create tags", () => { - const tokens = lex("@123 @@ @"); - equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "@123 @@ @", pos: 0 }, - ]); - }); - - it("Should allow escaping @ to prevent a tag creation", () => { - const tokens = lex("not a \\@tag"); - equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "not a @tag", pos: 0 }, - ]); - }); - - it("Should not mistake an email for a modifier tag", () => { - const tokens = lex("test@example.com"); - equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "test@example.com", pos: 0 }, - ]); - }); - - it("Should not mistake a scoped package for a tag", () => { - const tokens = lex("@typescript-eslint/parser @jest/globals"); equal(tokens, [ { kind: TokenSyntaxKind.Text, - text: "@typescript-eslint/parser @jest/globals", + text: "@123 @@ @ @tag @a @abc234", pos: 0, }, ]); }); - it("Should allow escaping @ in an email", () => { - const tokens = lex("test\\@example.com"); + it("Should allow escaping @ to prevent a tag creation", () => { + const tokens = lex("not a \\@tag"); equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "test@example.com", pos: 0 }, + { kind: TokenSyntaxKind.Text, text: "not a @tag", pos: 0 }, ]); }); @@ -1061,55 +1028,6 @@ describe("Raw Lexer", () => { ]); }); - it("Should handle tags after unclosed code", () => { - const tokens = lex( - dedent(` - Text - code? \`\` fake - @blockTag text - `), - ); - equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "Text", pos: 0 }, - { kind: TokenSyntaxKind.NewLine, text: "\n", pos: 4 }, - { kind: TokenSyntaxKind.Text, text: "code? `` fake", pos: 5 }, - { kind: TokenSyntaxKind.NewLine, text: "\n", pos: 18 }, - { kind: TokenSyntaxKind.Tag, text: "@blockTag", pos: 19 }, - { kind: TokenSyntaxKind.Text, text: " text", pos: 28 }, - ]); - }); - - it("Should handle a full comment", () => { - const tokens = lex( - dedent(` - This is a summary. - - @remarks - Detailed text here with a {@link Inline | inline link} - - @alpha @beta - `), - ).map((t) => ({ kind: t.kind, text: t.text })); - - equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "This is a summary." }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - { kind: TokenSyntaxKind.Tag, text: "@remarks" }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - { kind: TokenSyntaxKind.Text, text: "Detailed text here with a " }, - { kind: TokenSyntaxKind.OpenBrace, text: "{" }, - { kind: TokenSyntaxKind.Tag, text: "@link" }, - { kind: TokenSyntaxKind.Text, text: " Inline | inline link" }, - { kind: TokenSyntaxKind.CloseBrace, text: "}" }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - { kind: TokenSyntaxKind.Tag, text: "@alpha" }, - { kind: TokenSyntaxKind.Text, text: " " }, - { kind: TokenSyntaxKind.Tag, text: "@beta" }, - ]); - }); - it("Should handle unclosed code blocks", () => { const tokens = lex( dedent(` @@ -1119,120 +1037,47 @@ describe("Raw Lexer", () => { ); equal(tokens, [ - { kind: TokenSyntaxKind.Text, text: "Text", pos: 0 }, - { kind: TokenSyntaxKind.NewLine, text: "\n", pos: 4 }, + { kind: TokenSyntaxKind.Text, text: "Text\n", pos: 0 }, { kind: TokenSyntaxKind.Code, text: "```\nText", pos: 5 }, ]); }); - it("Should handle type annotations after tags at the start of a line", () => { - const tokens = lex(`@param {string} foo`); - - equal(tokens, [ - { kind: TokenSyntaxKind.Tag, text: "@param", pos: 0 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 6 }, - { kind: TokenSyntaxKind.TypeAnnotation, text: "{string}", pos: 7 }, - { kind: TokenSyntaxKind.Text, text: " foo", pos: 15 }, - ]); - }); - - it("Should handle type annotations containing string literals", () => { - const tokens = lex( - dedent(` - @param {"{{}}"} - @param {\`\${"{}"}\`} - @param {"text\\"more {}"} - @param {'{'} - EOF - `), - ); - - const expectedAnnotations = [ - '{"{{}}"}', - '{`${"{}"}`}', - '{"text\\"more {}"}', - "{'{'}", - ]; - - const expectedTokens = expectedAnnotations.flatMap((text) => [ - { kind: TokenSyntaxKind.Tag, text: "@param" }, - { kind: TokenSyntaxKind.Text, text: " " }, - { kind: TokenSyntaxKind.TypeAnnotation, text }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - ]); - expectedTokens.push({ kind: TokenSyntaxKind.Text, text: "EOF" }); - - equal( - tokens.map((t) => ({ kind: t.kind, text: t.text })), - expectedTokens, - ); - }); - - it("Should handle type annotations with object literals", () => { - const tokens = lex( - dedent(` - @param {{ a: string }} - @param {{ a: string; b: { c: { d: string }} }} - EOF - `), - ); - - const expectedAnnotations = [ - "{{ a: string }}", - "{{ a: string; b: { c: { d: string }} }}", - ]; - - const expectedTokens = expectedAnnotations.flatMap((text) => [ - { kind: TokenSyntaxKind.Tag, text: "@param" }, - { kind: TokenSyntaxKind.Text, text: " " }, - { kind: TokenSyntaxKind.TypeAnnotation, text }, - { kind: TokenSyntaxKind.NewLine, text: "\n" }, - ]); - expectedTokens.push({ kind: TokenSyntaxKind.Text, text: "EOF" }); - - equal( - tokens.map((t) => ({ kind: t.kind, text: t.text })), - expectedTokens, - ); - }); - - it("Should handle unclosed type annotations", () => { - const tokens = lex("@type {oops"); - equal(tokens, [ - { kind: TokenSyntaxKind.Tag, text: "@type", pos: 0 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 5 }, - { kind: TokenSyntaxKind.TypeAnnotation, text: "{oops", pos: 6 }, - ]); - }); - - it("Should not parse inline tags as types", () => { - const tokens = lex("@param { @link foo}"); + it("Should allow inline tags directly next to braces", () => { + const tokens = lex("{@inline}"); equal(tokens, [ - { kind: TokenSyntaxKind.Tag, text: "@param", pos: 0 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 6 }, - { kind: TokenSyntaxKind.OpenBrace, text: "{", pos: 7 }, - { kind: TokenSyntaxKind.Text, text: " ", pos: 8 }, - { kind: TokenSyntaxKind.Tag, text: "@link", pos: 9 }, - { kind: TokenSyntaxKind.Text, text: " foo", pos: 14 }, - { kind: TokenSyntaxKind.CloseBrace, text: "}", pos: 18 }, + { kind: TokenSyntaxKind.OpenBrace, text: "{", pos: 0 }, + { kind: TokenSyntaxKind.Tag, text: "@inline", pos: 1 }, + { kind: TokenSyntaxKind.CloseBrace, text: "}", pos: 8 }, ]); }); - it("Should allow inline tags directly next to braces", () => { - const tokens = lex("{@inline}"); + it("Should allow inline tags with spaces surrounding the braces", () => { + const tokens = lex("{ @link https://example.com example }"); equal(tokens, [ { kind: TokenSyntaxKind.OpenBrace, text: "{", pos: 0 }, - { kind: TokenSyntaxKind.Tag, text: "@inline", pos: 1 }, - { kind: TokenSyntaxKind.CloseBrace, text: "}", pos: 8 }, + { kind: TokenSyntaxKind.Text, text: " ", pos: 1 }, + { kind: TokenSyntaxKind.Tag, text: "@link", pos: 2 }, + { + kind: TokenSyntaxKind.Text, + text: " https://example.com example ", + pos: 7, + }, + { kind: TokenSyntaxKind.CloseBrace, text: "}", pos: 36 }, ]); }); }); describe("Comment Parser", () => { const config: CommentParserConfig = { - blockTags: new Set(["@param", "@remarks", "@module"]), + blockTags: new Set([ + "@param", + "@remarks", + "@module", + "@inheritDoc", + "@defaultValue", + ]), inlineTags: new Set(["@link"]), modifierTags: new Set([ "@public", @@ -1249,9 +1094,96 @@ describe("Comment Parser", () => { ignoreUnescapedBraces: false, inheritDocTag: false, }, + suppressCommentWarningsInDeclarationFiles: false, + useTsLinkResolution: false, + commentStyle: "jsdoc", }; + it("Should recognize @defaultValue as code", () => { + const files = new FileRegistry(); + const logger = new TestLogger(); + const file = "/** @defaultValue code */"; + const content = lexBlockComment(file); + const comment = parseComment( + content, + config, + new MinimalSourceFile(file, "<memory>"), + logger, + files, + ); + + equal( + comment, + new Comment( + [], + [ + new CommentTag("@defaultValue", [ + { kind: "code", text: "```ts\ncode\n```" }, + ]), + ], + ), + ); + logger.expectNoOtherMessages(); + }); + + it("Should recognize @defaultValue as not code if it contains an inline tag", () => { + const files = new FileRegistry(); + const logger = new TestLogger(); + const file = "/** @defaultValue text {@link foo} */"; + const content = lexBlockComment(file); + const comment = parseComment( + content, + config, + new MinimalSourceFile(file, "<memory>"), + logger, + files, + ); + + equal( + comment, + new Comment( + [], + [ + new CommentTag("@defaultValue", [ + { kind: "text", text: "text " }, + { kind: "inline-tag", tag: "@link", text: "foo" }, + ]), + ], + ), + ); + logger.expectNoOtherMessages(); + }); + + it("Should recognize @defaultValue as not code if it contains code", () => { + const files = new FileRegistry(); + const logger = new TestLogger(); + const file = "/** @defaultValue text `code` */"; + const content = lexBlockComment(file); + const comment = parseComment( + content, + config, + new MinimalSourceFile(file, "<memory>"), + logger, + files, + ); + + equal( + comment, + new Comment( + [], + [ + new CommentTag("@defaultValue", [ + { kind: "text", text: "text " }, + { kind: "code", text: "`code`" }, + ]), + ], + ), + ); + logger.expectNoOtherMessages(); + }); + it("Should rewrite @inheritdoc to @inheritDoc", () => { + const files = new FileRegistry(); const logger = new TestLogger(); const file = "/** @inheritdoc */"; const content = lexBlockComment(file); @@ -1260,6 +1192,7 @@ describe("Comment Parser", () => { config, new MinimalSourceFile(file, "<memory>"), logger, + files, ); logger.expectMessage( @@ -1269,7 +1202,9 @@ describe("Comment Parser", () => { equal(comment, new Comment([], [new CommentTag("@inheritDoc", [])])); }); + let files: FileRegistry; function getComment(text: string) { + files = new FileRegistry(); const logger = new TestLogger(); const content = lexBlockComment(text); const comment = parseComment( @@ -1277,11 +1212,16 @@ describe("Comment Parser", () => { config, new MinimalSourceFile(text, "<memory>"), logger, + files, ); logger.expectNoOtherMessages(); return comment; } + afterEach(() => { + files = undefined!; + }); + it("Simple summary", () => { const comment = getComment("/** Summary! */"); equal(comment.summary, [{ kind: "text", text: "Summary!" }]); @@ -1415,6 +1355,161 @@ describe("Comment Parser", () => { equal(comment.blockTags, [tag]); equal(comment.modifierTags, new Set()); }); + + it("Recognizes markdown links", () => { + const comment = getComment(`/** + * [text](./relative.md) ![](image.png) + * Not relative: [passwd](/etc/passwd) [Windows](C:\\\\\\\\Windows) [example.com](http://example.com) [hash](#hash) + */`); + + equal(comment.summary, [ + { kind: "text", text: "[text](" }, + { kind: "relative-link", text: "./relative.md", target: 1 }, + { kind: "text", text: ") ![](" }, + { kind: "relative-link", text: "image.png", target: 2 }, + { + kind: "text", + text: ")\nNot relative: [passwd](/etc/passwd) [Windows](C:\\\\\\\\Windows) [example.com](http://example.com) [hash](#hash)", + }, + ] satisfies CommentDisplayPart[]); + }); + + it("#2606 Recognizes markdown links which contain inline code in the label", () => { + const comment = getComment(`/** + * [\`text\`](./relative.md) + * [\`text\` + * more](./relative.md) + * [\`text\` + * + * more](./relative.md) + */`); + + equal(comment.summary, [ + // Simple case with code + { kind: "text", text: "[" }, + { kind: "code", text: "`text`" }, + { kind: "text", text: "](" }, + { kind: "relative-link", text: "./relative.md", target: 1 }, + // Labels can also include single newlines + { kind: "text", text: ")\n[" }, + { kind: "code", text: "`text`" }, + { kind: "text", text: "\nmore](" }, + { kind: "relative-link", text: "./relative.md", target: 1 }, + // But not double! + { kind: "text", text: ")\n[" }, + { kind: "code", text: "`text`" }, + { kind: "text", text: "\n\nmore](./relative.md)" }, + ] satisfies CommentDisplayPart[]); + }); + + it("Recognizes markdown links which contain inline code in the label", () => { + const comment = getComment(`/** + * [\`text\`](./relative.md) + */`); + + equal(comment.summary, [ + { kind: "text", text: "[" }, + { kind: "code", text: "`text`" }, + { kind: "text", text: "](" }, + { kind: "relative-link", text: "./relative.md", target: 1 }, + { kind: "text", text: ")" }, + ] satisfies CommentDisplayPart[]); + }); + + it("Recognizes markdown reference definition blocks", () => { + const comment = getComment(`/** + * [1]: ./example.md + * [2]:<./example with space> + * [3]: https://example.com + * [4]: #hash + */`); + + equal(comment.summary, [ + { kind: "text", text: "[1]: " }, + { kind: "relative-link", text: "./example.md", target: 1 }, + { kind: "text", text: "\n[2]:" }, + { + kind: "relative-link", + text: "<./example with space>", + target: 2, + }, + { + kind: "text", + text: "\n[3]: https://example.com\n[4]: #hash", + }, + ] satisfies CommentDisplayPart[]); + }); + + it("Does not mistake mailto: links as relative paths", () => { + const comment = getComment(`/** + * [1]: mailto:example@example.com + */`); + + equal(comment.summary, [ + { kind: "text", text: "[1]: mailto:example@example.com" }, + ] satisfies CommentDisplayPart[]); + }); + + it("Recognizes HTML image links", () => { + const comment = getComment(`/** + * <img width=100 height="200" src="./test.png" > + * <img src="./test space.png"/> + * <img src="https://example.com/favicon.ico"> + */`); + + equal(comment.summary, [ + { kind: "text", text: '<img width=100 height="200" src="' }, + { kind: "relative-link", text: "./test.png", target: 1 }, + { kind: "text", text: '" >\n<img src="' }, + { + kind: "relative-link", + text: "./test space.png", + target: 2, + }, + { + kind: "text", + text: '"/>\n<img src="https://example.com/favicon.ico">', + }, + ] satisfies CommentDisplayPart[]); + }); + + it("Recognizes HTML anchor links", () => { + const comment = getComment(`/** + * <a data-foo="./path.txt" href="./test.png" > + * <a href="./test space.png"/> + * <a href="https://example.com/favicon.ico"> + * <a href="#hash"> + */`); + + equal(comment.summary, [ + { kind: "text", text: '<a data-foo="./path.txt" href="' }, + { kind: "relative-link", text: "./test.png", target: 1 }, + { kind: "text", text: '" >\n<a href="' }, + { + kind: "relative-link", + text: "./test space.png", + target: 2, + }, + { + kind: "text", + text: '"/>\n<a href="https://example.com/favicon.ico">\n<a href="#hash">', + }, + ] satisfies CommentDisplayPart[]); + }); + + it("Properly handles character escapes", () => { + const comment = getComment(`/** + * <a href="./&a.png" > + */`); + + equal(comment.summary, [ + { kind: "text", text: '<a href="' }, + { kind: "relative-link", text: "./&a.png", target: 1 }, + { kind: "text", text: '" >' }, + ] satisfies CommentDisplayPart[]); + + equal(files.getName(1), "&a.png"); + }); }); describe("extractTagName", () => { diff --git a/src/test/converter.test.ts b/src/test/converter.test.ts index cecbc29a3..49ab152e0 100644 --- a/src/test/converter.test.ts +++ b/src/test/converter.test.ts @@ -8,21 +8,28 @@ import { Reflection, ReflectionCategory, ReflectionGroup, - JSONOutput, + type JSONOutput, CommentTag, ReferenceType, Comment, - CommentDisplayPart, + type CommentDisplayPart, SourceReference, ReferenceReflection, } from ".."; -import type { ModelToObject } from "../lib/serialization/schema"; +import type { + SomeReflection, + ModelToObject, +} from "../lib/serialization/schema"; import { getExpandedEntryPointsForPaths } from "../lib/utils"; import { getConverterApp, getConverterBase, getConverterProgram, } from "./programs"; +import { + FileRegistry, + ValidatingFileRegistry, +} from "../lib/models/FileRegistry"; const comparisonSerializer = new Serializer(); comparisonSerializer.addSerializer({ @@ -54,7 +61,7 @@ comparisonSerializer.addSerializer({ return obj; }, }); -comparisonSerializer.addSerializer({ +comparisonSerializer.addSerializer<CommentTag>({ priority: 0, supports(x) { return x instanceof CommentTag; @@ -73,7 +80,7 @@ comparisonSerializer.addSerializer({ return obj; }, }); -comparisonSerializer.addSerializer({ +comparisonSerializer.addSerializer<SomeReflection>({ priority: 0, supports(x) { return x instanceof Reflection; @@ -99,7 +106,7 @@ comparisonSerializer.addSerializer({ return x instanceof ReflectionCategory || x instanceof ReflectionGroup; }, toObject(refl: ReflectionCategory | ReflectionGroup, obj: any) { - obj.children = refl.children?.map((c) => c.getFullName()); + obj.children = refl.children.map((c) => c.getFullName()); return obj; }, }); @@ -128,7 +135,7 @@ comparisonSerializer.addSerializer({ }, toObject(project: ProjectReflection, obj: JSONOutput.ProjectReflection) { const idMap: Record<string, JSONOutput.ReflectionSymbolId> = {}; - for (const [k, v] of Object.entries(obj.symbolIdMap!)) { + for (const [k, v] of Object.entries(obj.symbolIdMap || {})) { idMap[project.getReflectionById(+k)!.getFullName()] = v; } obj.symbolIdMap = idMap; @@ -136,6 +143,16 @@ comparisonSerializer.addSerializer({ return obj; }, }); +comparisonSerializer.addSerializer({ + priority: -1, + supports(obj) { + return obj instanceof FileRegistry; + }, + toObject(_media: FileRegistry, obj: JSONOutput.FileRegistry) { + obj.reflections = {}; + return obj; + }, +}); describe("Converter", function () { const base = getConverterBase(); @@ -187,6 +204,7 @@ describe("Converter", function () { it(`[${file}] converts fixtures`, function () { before(); resetReflectionID(); + app.files = new ValidatingFileRegistry(); const entryPoints = getExpandedEntryPointsForPaths( app.logger, [path], @@ -217,7 +235,12 @@ describe("Converter", function () { }); it(`[${file}] round trips revival`, () => { - const revived = app.deserializer.reviveProject(specs); + const revived = app.deserializer.reviveProject( + specs, + specs.name, + process.cwd(), + new FileRegistry(), + ); const specs2 = JSON.parse( JSON.stringify( comparisonSerializer.projectToObject( diff --git a/src/test/converter/alias/specs.json b/src/test/converter/alias/specs.json index 1d1bc0050..45f545373 100644 --- a/src/test/converter/alias/specs.json +++ b/src/test/converter/alias/specs.json @@ -362,5 +362,13 @@ "sourceFileName": "src/test/converter/alias/alias.ts", "qualifiedName": "MergedCrossReference" } + }, + "files": { + "entries": { + "1": "src/test/converter/alias/alias.ts" + }, + "reflections": { + "1": 0 + } } } diff --git a/src/test/converter/class/specs-with-lump-categories.json b/src/test/converter/class/specs-with-lump-categories.json index 4b308824e..ebdd6b2d3 100644 --- a/src/test/converter/class/specs-with-lump-categories.json +++ b/src/test/converter/class/specs-with-lump-categories.json @@ -1803,7 +1803,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -1837,7 +1838,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isStatic": true + "isStatic": true, + "isInherited": true }, "comment": { "summary": [ @@ -1872,7 +1874,9 @@ "name": "arrowMethod", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "class.ts", @@ -1887,7 +1891,9 @@ "name": "arrowMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -2083,7 +2089,8 @@ "variant": "declaration", "kind": 2048, "flags": { - "isStatic": true + "isStatic": true, + "isInherited": true }, "sources": [ { @@ -2099,7 +2106,9 @@ "name": "staticMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -2746,7 +2755,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isReadonly": true + "isReadonly": true, + "isInherited": true }, "comment": { "summary": [ @@ -2780,7 +2790,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -2814,7 +2825,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -4089,7 +4101,7 @@ "url": "typedoc://generic-class.ts#L20" } ], - "typeParameter": [ + "typeParameters": [ { "id": 170, "name": "T", @@ -4423,7 +4435,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isProtected": true + "isProtected": true, + "isInherited": true }, "comment": { "summary": [ @@ -4457,7 +4470,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isProtected": true + "isProtected": true, + "isInherited": true }, "comment": { "summary": [ @@ -4493,7 +4507,9 @@ "name": "getValue", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "generic-class.ts", @@ -4508,7 +4524,9 @@ "name": "getValue", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -5031,7 +5049,7 @@ "variant": "signature", "kind": 16384, "flags": {}, - "typeParameter": [ + "typeParameters": [ { "id": 214, "name": "T", @@ -6066,5 +6084,29 @@ "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" } + }, + "files": { + "entries": { + "1": "src/test/converter/class/access.ts", + "2": "src/test/converter/class/class.ts", + "3": "src/test/converter/class/constructor-properties.ts", + "4": "src/test/converter/class/decorators.ts", + "5": "src/test/converter/class/events-overloads.ts", + "6": "src/test/converter/class/events.ts", + "7": "src/test/converter/class/generic-class.ts", + "8": "src/test/converter/class/getter-setter.ts", + "9": "src/test/converter/class/type-operator.ts" + }, + "reflections": { + "1": 1, + "2": 21, + "3": 94, + "4": 115, + "5": 134, + "6": 161, + "7": 166, + "8": 185, + "9": 205 + } } } diff --git a/src/test/converter/class/specs.json b/src/test/converter/class/specs.json index 4b308824e..ebdd6b2d3 100644 --- a/src/test/converter/class/specs.json +++ b/src/test/converter/class/specs.json @@ -1803,7 +1803,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -1837,7 +1838,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isStatic": true + "isStatic": true, + "isInherited": true }, "comment": { "summary": [ @@ -1872,7 +1874,9 @@ "name": "arrowMethod", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "class.ts", @@ -1887,7 +1891,9 @@ "name": "arrowMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -2083,7 +2089,8 @@ "variant": "declaration", "kind": 2048, "flags": { - "isStatic": true + "isStatic": true, + "isInherited": true }, "sources": [ { @@ -2099,7 +2106,9 @@ "name": "staticMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -2746,7 +2755,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isReadonly": true + "isReadonly": true, + "isInherited": true }, "comment": { "summary": [ @@ -2780,7 +2790,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -2814,7 +2825,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isPublic": true + "isPublic": true, + "isInherited": true }, "comment": { "summary": [ @@ -4089,7 +4101,7 @@ "url": "typedoc://generic-class.ts#L20" } ], - "typeParameter": [ + "typeParameters": [ { "id": 170, "name": "T", @@ -4423,7 +4435,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isProtected": true + "isProtected": true, + "isInherited": true }, "comment": { "summary": [ @@ -4457,7 +4470,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isProtected": true + "isProtected": true, + "isInherited": true }, "comment": { "summary": [ @@ -4493,7 +4507,9 @@ "name": "getValue", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "generic-class.ts", @@ -4508,7 +4524,9 @@ "name": "getValue", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "comment": { "summary": [ { @@ -5031,7 +5049,7 @@ "variant": "signature", "kind": 16384, "flags": {}, - "typeParameter": [ + "typeParameters": [ { "id": 214, "name": "T", @@ -6066,5 +6084,29 @@ "sourceFileName": "src/test/converter/class/type-operator.ts", "qualifiedName": "GenericClass.T" } + }, + "files": { + "entries": { + "1": "src/test/converter/class/access.ts", + "2": "src/test/converter/class/class.ts", + "3": "src/test/converter/class/constructor-properties.ts", + "4": "src/test/converter/class/decorators.ts", + "5": "src/test/converter/class/events-overloads.ts", + "6": "src/test/converter/class/events.ts", + "7": "src/test/converter/class/generic-class.ts", + "8": "src/test/converter/class/getter-setter.ts", + "9": "src/test/converter/class/type-operator.ts" + }, + "reflections": { + "1": 1, + "2": 21, + "3": 94, + "4": 115, + "5": 134, + "6": 161, + "7": 166, + "8": 185, + "9": 205 + } } } diff --git a/src/test/converter/comment/comment.ts b/src/test/converter/comment/comment.ts index 8f84e9263..04898ae8b 100644 --- a/src/test/converter/comment/comment.ts +++ b/src/test/converter/comment/comment.ts @@ -25,7 +25,6 @@ import "./comment2"; * } * ``` * @deprecated - * @todo something * * @type {Data<object>} will also be removed * @@ -84,3 +83,13 @@ export class CommentedClass { */ ignoredprop: string; } + +export type UnionWithCommentsOnMembers = + /** + * Doc of foo1. + */ + | "foo1" + /** + * Doc of foo2. + */ + | "foo2"; diff --git a/src/test/converter/comment/specs.json b/src/test/converter/comment/specs.json index 81789794b..883a708cc 100644 --- a/src/test/converter/comment/specs.json +++ b/src/test/converter/comment/specs.json @@ -33,15 +33,6 @@ { "tag": "@deprecated", "content": [] - }, - { - "tag": "@todo", - "content": [ - { - "kind": "text", - "text": "something" - } - ] } ] }, @@ -85,9 +76,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 39, + "line": 38, "character": 4, - "url": "typedoc://comment.ts#L39" + "url": "typedoc://comment.ts#L38" } ], "type": { @@ -104,9 +95,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 79, + "line": 78, "character": 4, - "url": "typedoc://comment.ts#L79" + "url": "typedoc://comment.ts#L78" } ], "signatures": [ @@ -127,9 +118,9 @@ "sources": [ { "fileName": "comment.ts", - "line": 79, + "line": 78, "character": 4, - "url": "typedoc://comment.ts#L79" + "url": "typedoc://comment.ts#L78" } ], "parameters": [ @@ -193,11 +184,53 @@ "sources": [ { "fileName": "comment.ts", - "line": 35, + "line": 34, "character": 13, - "url": "typedoc://comment.ts#L35" + "url": "typedoc://comment.ts#L34" } ] + }, + { + "id": 26, + "name": "UnionWithCommentsOnMembers", + "variant": "declaration", + "kind": 2097152, + "flags": {}, + "sources": [ + { + "fileName": "comment.ts", + "line": 87, + "character": 12, + "url": "typedoc://comment.ts#L87" + } + ], + "type": { + "type": "union", + "types": [ + { + "type": "literal", + "value": "foo1" + }, + { + "type": "literal", + "value": "foo2" + } + ], + "elementSummaries": [ + [ + { + "kind": "text", + "text": "Doc of foo1." + } + ], + [ + { + "kind": "text", + "text": "Doc of foo2." + } + ] + ] + } } ], "groups": [ @@ -206,6 +239,12 @@ "children": [ 2 ] + }, + { + "title": "Type Aliases", + "children": [ + 26 + ] } ], "sources": [ @@ -218,7 +257,7 @@ ] }, { - "id": 26, + "id": 27, "name": "comment2", "variant": "declaration", "kind": 2, @@ -241,7 +280,7 @@ }, "children": [ { - "id": 27, + "id": 28, "name": "multiply", "variant": "declaration", "kind": 64, @@ -256,7 +295,7 @@ ], "signatures": [ { - "id": 28, + "id": 29, "name": "multiply", "variant": "signature", "kind": 4096, @@ -271,7 +310,7 @@ ], "parameters": [ { - "id": 29, + "id": 30, "name": "a", "variant": "param", "kind": 32768, @@ -282,7 +321,7 @@ } }, { - "id": 30, + "id": 31, "name": "b", "variant": "param", "kind": 32768, @@ -305,7 +344,7 @@ { "title": "Functions", "children": [ - 27 + 28 ] } ], @@ -319,7 +358,7 @@ ] }, { - "id": 31, + "id": 32, "name": "comment3", "variant": "declaration", "kind": 2, @@ -342,7 +381,7 @@ }, "children": [ { - "id": 32, + "id": 33, "name": "multiply", "variant": "declaration", "kind": 64, @@ -357,7 +396,7 @@ ], "signatures": [ { - "id": 33, + "id": 34, "name": "multiply", "variant": "signature", "kind": 4096, @@ -372,7 +411,7 @@ ], "parameters": [ { - "id": 34, + "id": 35, "name": "a", "variant": "param", "kind": 32768, @@ -383,7 +422,7 @@ } }, { - "id": 35, + "id": 36, "name": "b", "variant": "param", "kind": 32768, @@ -406,7 +445,7 @@ { "title": "Functions", "children": [ - 32 + 33 ] } ], @@ -420,7 +459,7 @@ ] }, { - "id": 36, + "id": 37, "name": "comment4", "variant": "declaration", "kind": 2, @@ -439,7 +478,7 @@ }, "children": [ { - "id": 37, + "id": 38, "name": "multiply", "variant": "declaration", "kind": 64, @@ -454,7 +493,7 @@ ], "signatures": [ { - "id": 38, + "id": 39, "name": "multiply", "variant": "signature", "kind": 4096, @@ -469,7 +508,7 @@ ], "parameters": [ { - "id": 39, + "id": 40, "name": "a", "variant": "param", "kind": 32768, @@ -480,7 +519,7 @@ } }, { - "id": 40, + "id": 41, "name": "b", "variant": "param", "kind": 32768, @@ -503,7 +542,7 @@ { "title": "Functions", "children": [ - 37 + 38 ] } ], @@ -522,9 +561,9 @@ "title": "Modules", "children": [ 1, - 26, - 31, - 36 + 27, + 32, + 37 ] } ], @@ -559,12 +598,12 @@ "qualifiedName": "arg2" }, "26": { - "sourceFileName": "src/test/converter/comment/comment2.ts", - "qualifiedName": "" + "sourceFileName": "src/test/converter/comment/comment.ts", + "qualifiedName": "UnionWithCommentsOnMembers" }, "27": { "sourceFileName": "src/test/converter/comment/comment2.ts", - "qualifiedName": "multiply" + "qualifiedName": "" }, "28": { "sourceFileName": "src/test/converter/comment/comment2.ts", @@ -572,19 +611,19 @@ }, "29": { "sourceFileName": "src/test/converter/comment/comment2.ts", - "qualifiedName": "a" + "qualifiedName": "multiply" }, "30": { "sourceFileName": "src/test/converter/comment/comment2.ts", - "qualifiedName": "b" + "qualifiedName": "a" }, "31": { - "sourceFileName": "src/test/converter/comment/comment3.ts", - "qualifiedName": "" + "sourceFileName": "src/test/converter/comment/comment2.ts", + "qualifiedName": "b" }, "32": { "sourceFileName": "src/test/converter/comment/comment3.ts", - "qualifiedName": "multiply" + "qualifiedName": "" }, "33": { "sourceFileName": "src/test/converter/comment/comment3.ts", @@ -592,19 +631,19 @@ }, "34": { "sourceFileName": "src/test/converter/comment/comment3.ts", - "qualifiedName": "a" + "qualifiedName": "multiply" }, "35": { "sourceFileName": "src/test/converter/comment/comment3.ts", - "qualifiedName": "b" + "qualifiedName": "a" }, "36": { - "sourceFileName": "src/test/converter/comment/comment4.ts", - "qualifiedName": "" + "sourceFileName": "src/test/converter/comment/comment3.ts", + "qualifiedName": "b" }, "37": { "sourceFileName": "src/test/converter/comment/comment4.ts", - "qualifiedName": "multiply" + "qualifiedName": "" }, "38": { "sourceFileName": "src/test/converter/comment/comment4.ts", @@ -612,11 +651,29 @@ }, "39": { "sourceFileName": "src/test/converter/comment/comment4.ts", - "qualifiedName": "a" + "qualifiedName": "multiply" }, "40": { + "sourceFileName": "src/test/converter/comment/comment4.ts", + "qualifiedName": "a" + }, + "41": { "sourceFileName": "src/test/converter/comment/comment4.ts", "qualifiedName": "b" } + }, + "files": { + "entries": { + "1": "src/test/converter/comment/comment.ts", + "2": "src/test/converter/comment/comment2.ts", + "3": "src/test/converter/comment/comment3.ts", + "4": "src/test/converter/comment/comment4.ts" + }, + "reflections": { + "1": 1, + "2": 27, + "3": 32, + "4": 37 + } } } diff --git a/src/test/converter/declaration/specs.json b/src/test/converter/declaration/specs.json index 55a422a3f..e8e9d729e 100644 --- a/src/test/converter/declaration/specs.json +++ b/src/test/converter/declaration/specs.json @@ -349,5 +349,17 @@ "sourceFileName": "src/test/converter/declaration/external.d.ts", "qualifiedName": "ActionSet" } + }, + "files": { + "entries": { + "1": "src/test/converter/declaration/declaration.d.ts", + "2": "src/test/converter/declaration/export-declaration.d.ts", + "3": "src/test/converter/declaration/external.d.ts" + }, + "reflections": { + "1": 1, + "2": 7, + "3": 14 + } } } diff --git a/src/test/converter/enum/specs.json b/src/test/converter/enum/specs.json index c9efd3c7c..624069bcc 100644 --- a/src/test/converter/enum/specs.json +++ b/src/test/converter/enum/specs.json @@ -516,5 +516,13 @@ "sourceFileName": "src/test/converter/enum/enum.ts", "qualifiedName": "ConstEnum.b" } + }, + "files": { + "entries": { + "1": "src/test/converter/enum/enum.ts" + }, + "reflections": { + "1": 0 + } } } diff --git a/src/test/converter/enum/specs.nodoc.json b/src/test/converter/enum/specs.nodoc.json index c9efd3c7c..624069bcc 100644 --- a/src/test/converter/enum/specs.nodoc.json +++ b/src/test/converter/enum/specs.nodoc.json @@ -516,5 +516,13 @@ "sourceFileName": "src/test/converter/enum/enum.ts", "qualifiedName": "ConstEnum.b" } + }, + "files": { + "entries": { + "1": "src/test/converter/enum/enum.ts" + }, + "reflections": { + "1": 0 + } } } diff --git a/src/test/converter/exports/specs.json b/src/test/converter/exports/specs.json index 6e8c044dd..e888ba424 100644 --- a/src/test/converter/exports/specs.json +++ b/src/test/converter/exports/specs.json @@ -1161,5 +1161,23 @@ "sourceFileName": "src/test/converter/exports/mod.ts", "qualifiedName": "GH1453Helper" } + }, + "files": { + "entries": { + "1": "src/test/converter/exports/export-assignment.ts", + "2": "src/test/converter/exports/export-default.ts", + "3": "src/test/converter/exports/export-with-local.ts", + "4": "src/test/converter/exports/export.ts", + "5": "src/test/converter/exports/mod.ts", + "6": "src/test/converter/exports/no-doc-members.ts" + }, + "reflections": { + "1": 1, + "2": 6, + "3": 8, + "4": 14, + "5": 29, + "6": 38 + } } } diff --git a/src/test/converter/exports/specs.nodoc.json b/src/test/converter/exports/specs.nodoc.json index 731cc3279..efa9f878e 100644 --- a/src/test/converter/exports/specs.nodoc.json +++ b/src/test/converter/exports/specs.nodoc.json @@ -326,5 +326,20 @@ "sourceFileName": "src/test/converter/exports/mod.ts", "qualifiedName": "b" } + }, + "files": { + "entries": { + "1": "src/test/converter/exports/export-assignment.ts", + "2": "src/test/converter/exports/export-default.ts", + "3": "src/test/converter/exports/export-with-local.ts", + "4": "src/test/converter/exports/export.ts", + "5": "src/test/converter/exports/mod.ts", + "6": "src/test/converter/exports/no-doc-members.ts" + }, + "reflections": { + "4": 14, + "5": 29, + "6": 38 + } } } diff --git a/src/test/converter/function/function.ts b/src/test/converter/function/function.ts index c0a4ed128..c07486051 100644 --- a/src/test/converter/function/function.ts +++ b/src/test/converter/function/function.ts @@ -95,12 +95,12 @@ export function multipleSignatures(value: string): string; export function multipleSignatures(value: { name: string }): string; /** - * This is the actual implementation, this comment will not be visible - * in the generated documentation. The `@inheritdoc` tag can not be used - * to pull content from this signature into documentation for the real - * signatures. - * - * @return This is the return value of the function. + * This comment is on the actual implementation of the function. + * TypeDoc used to allow this for providing "default" comments that would be + * copied to each signature. It no longer does this, and instead treats + * this comment as belonging to the function reflection itself. + * Any `@param` or `@returns` tags within this comment won't be applied + * to signatures. */ export function multipleSignatures(): string { if (arguments.length > 0) { diff --git a/src/test/converter/function/specs.json b/src/test/converter/function/specs.json index 95c195124..c4c9a1ffd 100644 --- a/src/test/converter/function/specs.json +++ b/src/test/converter/function/specs.json @@ -425,6 +425,14 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Returns true if fn returns true for every item in the iterator\n\nReturns true if the iterator is empty" + } + ] + }, "sources": [ { "fileName": "function.ts", @@ -440,14 +448,6 @@ "variant": "signature", "kind": 4096, "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Returns true if fn returns true for every item in the iterator\n\nReturns true if the iterator is empty" - } - ] - }, "sources": [ { "fileName": "function.ts", @@ -456,7 +456,7 @@ "url": "typedoc://function.ts#L198" } ], - "typeParameter": [ + "typeParameters": [ { "id": 78, "name": "T", @@ -565,14 +565,6 @@ "variant": "signature", "kind": 4096, "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Returns true if fn returns true for every item in the iterator\n\nReturns true if the iterator is empty" - } - ] - }, "sources": [ { "fileName": "function.ts", @@ -581,7 +573,7 @@ "url": "typedoc://function.ts#L199" } ], - "typeParameter": [ + "typeParameters": [ { "id": 85, "name": "T", @@ -760,7 +752,7 @@ "url": "typedoc://function.ts#L145" } ], - "typeParameter": [ + "typeParameters": [ { "id": 47, "name": "T", @@ -1527,7 +1519,7 @@ "url": "typedoc://function.ts#L157" } ], - "typeParameter": [ + "typeParameters": [ { "id": 51, "name": "T", @@ -1745,6 +1737,30 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This comment is on the actual implementation of the function.\nTypeDoc used to allow this for providing \"default\" comments that would be\ncopied to each signature. It no longer does this, and instead treats\nthis comment as belonging to the function reflection itself.\nAny " + }, + { + "kind": "code", + "text": "`@param`" + }, + { + "kind": "text", + "text": " or " + }, + { + "kind": "code", + "text": "`@returns`" + }, + { + "kind": "text", + "text": " tags within this comment won't be applied\nto signatures." + } + ] + }, "sources": [ { "fileName": "function.ts", @@ -1921,6 +1937,14 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "This is a function that is assigned to a variable." + } + ] + }, "sources": [ { "fileName": "function.ts", @@ -1937,12 +1961,7 @@ "kind": 4096, "flags": {}, "comment": { - "summary": [ - { - "kind": "text", - "text": "This is a function that is assigned to a variable." - } - ], + "summary": [], "blockTags": [ { "tag": "@returns", @@ -2090,7 +2109,7 @@ "url": "typedoc://generic-function.ts#L18" } ], - "typeParameter": [ + "typeParameters": [ { "id": 100, "name": "T", @@ -2190,7 +2209,7 @@ "url": "typedoc://generic-function.ts#L29" } ], - "typeParameter": [ + "typeParameters": [ { "id": 105, "name": "T", @@ -2289,7 +2308,7 @@ "url": "typedoc://generic-function.ts#L7" } ], - "typeParameter": [ + "typeParameters": [ { "id": 96, "name": "T", @@ -3110,5 +3129,17 @@ "sourceFileName": "src/test/converter/function/implicit-types.ts", "qualifiedName": "BreakpointRange.end" } + }, + "files": { + "entries": { + "1": "src/test/converter/function/function.ts", + "2": "src/test/converter/function/generic-function.ts", + "3": "src/test/converter/function/implicit-types.ts" + }, + "reflections": { + "1": 1, + "2": 93, + "3": 107 + } } } diff --git a/src/test/converter/inherit-param-doc/inherit-param-doc.ts b/src/test/converter/inherit-param-doc/inherit-param-doc.ts index 50ece38d1..58fc3f69b 100644 --- a/src/test/converter/inherit-param-doc/inherit-param-doc.ts +++ b/src/test/converter/inherit-param-doc/inherit-param-doc.ts @@ -4,11 +4,18 @@ export interface Base { * @param b - Parameter B. */ method1(a: number, b: string): void; + /** + * @param a - Parameter A. + * @param b - Parameter B. + */ + method2(a: number, b: string): void; } export class Class1 implements Base { /** @inheritDoc */ method1(a: number, b: string): void {} + /** @inheritDoc */ + method2(): void {} } export class Class2 implements Base { @@ -18,6 +25,12 @@ export class Class2 implements Base { * @param a - Custom parameter A doc. */ method1(a: number, b: string): void {} + /** + * @inheritDoc + * + * @param a - Custom parameter A doc. + */ + method2(a: number): void {} } export class Class3 implements Base { @@ -27,4 +40,5 @@ export class Class3 implements Base { * @param c - Custom second parameter doc with name change. */ method1(a: number, c: string): void {} + method2(a: number): void {} } diff --git a/src/test/converter/inherit-param-doc/specs.json b/src/test/converter/inherit-param-doc/specs.json index e03b60da2..264c557bf 100644 --- a/src/test/converter/inherit-param-doc/specs.json +++ b/src/test/converter/inherit-param-doc/specs.json @@ -6,28 +6,28 @@ "flags": {}, "children": [ { - "id": 6, + "id": 10, "name": "Class1", "variant": "declaration", "kind": 128, "flags": {}, "children": [ { - "id": 7, + "id": 11, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 8, + "id": 12, "name": "new Class1", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 6, + "target": 10, "name": "Class1", "package": "typedoc" } @@ -35,7 +35,7 @@ ] }, { - "id": 9, + "id": 13, "name": "method1", "variant": "declaration", "kind": 2048, @@ -43,14 +43,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 11, + "line": 16, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L11" + "url": "typedoc://inherit-param-doc.ts#L16" } ], "signatures": [ { - "id": 10, + "id": 14, "name": "method1", "variant": "signature", "kind": 4096, @@ -58,14 +58,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 11, + "line": 16, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L11" + "url": "typedoc://inherit-param-doc.ts#L16" } ], "parameters": [ { - "id": 11, + "id": 15, "name": "a", "variant": "param", "kind": 32768, @@ -84,7 +84,7 @@ } }, { - "id": 12, + "id": 16, "name": "b", "variant": "param", "kind": 32768, @@ -119,28 +119,75 @@ "target": 2, "name": "Base.method1" } + }, + { + "id": 17, + "name": "method2", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 18, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L18" + } + ], + "signatures": [ + { + "id": 18, + "name": "method2", + "variant": "signature", + "kind": 4096, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 18, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L18" + } + ], + "type": { + "type": "intrinsic", + "name": "void" + }, + "implementationOf": { + "type": "reference", + "target": 7, + "name": "Base.method2" + } + } + ], + "implementationOf": { + "type": "reference", + "target": 6, + "name": "Base.method2" + } } ], "groups": [ { "title": "Constructors", "children": [ - 7 + 11 ] }, { "title": "Methods", "children": [ - 9 + 13, + 17 ] } ], "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 9, + "line": 14, "character": 13, - "url": "typedoc://inherit-param-doc.ts#L9" + "url": "typedoc://inherit-param-doc.ts#L14" } ], "implementedTypes": [ @@ -153,28 +200,28 @@ ] }, { - "id": 13, + "id": 19, "name": "Class2", "variant": "declaration", "kind": 128, "flags": {}, "children": [ { - "id": 14, + "id": 20, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 15, + "id": 21, "name": "new Class2", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 13, + "target": 19, "name": "Class2", "package": "typedoc" } @@ -182,7 +229,7 @@ ] }, { - "id": 16, + "id": 22, "name": "method1", "variant": "declaration", "kind": 2048, @@ -190,14 +237,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 20, + "line": 27, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L20" + "url": "typedoc://inherit-param-doc.ts#L27" } ], "signatures": [ { - "id": 17, + "id": 23, "name": "method1", "variant": "signature", "kind": 4096, @@ -205,14 +252,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 20, + "line": 27, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L20" + "url": "typedoc://inherit-param-doc.ts#L27" } ], "parameters": [ { - "id": 18, + "id": 24, "name": "a", "variant": "param", "kind": 32768, @@ -231,7 +278,7 @@ } }, { - "id": 19, + "id": 25, "name": "b", "variant": "param", "kind": 32768, @@ -266,28 +313,96 @@ "target": 2, "name": "Base.method1" } + }, + { + "id": 26, + "name": "method2", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 33, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L33" + } + ], + "signatures": [ + { + "id": 27, + "name": "method2", + "variant": "signature", + "kind": 4096, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 33, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L33" + } + ], + "parameters": [ + { + "id": 28, + "name": "a", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Custom parameter A doc." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "number" + } + } + ], + "type": { + "type": "intrinsic", + "name": "void" + }, + "implementationOf": { + "type": "reference", + "target": 7, + "name": "Base.method2" + } + } + ], + "implementationOf": { + "type": "reference", + "target": 6, + "name": "Base.method2" + } } ], "groups": [ { "title": "Constructors", "children": [ - 14 + 20 ] }, { "title": "Methods", "children": [ - 16 + 22, + 26 ] } ], "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 14, + "line": 21, "character": 13, - "url": "typedoc://inherit-param-doc.ts#L14" + "url": "typedoc://inherit-param-doc.ts#L21" } ], "implementedTypes": [ @@ -300,28 +415,28 @@ ] }, { - "id": 20, + "id": 29, "name": "Class3", "variant": "declaration", "kind": 128, "flags": {}, "children": [ { - "id": 21, + "id": 30, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 22, + "id": 31, "name": "new Class3", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 20, + "target": 29, "name": "Class3", "package": "typedoc" } @@ -329,7 +444,7 @@ ] }, { - "id": 23, + "id": 32, "name": "method1", "variant": "declaration", "kind": 2048, @@ -337,14 +452,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 29, + "line": 42, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L29" + "url": "typedoc://inherit-param-doc.ts#L42" } ], "signatures": [ { - "id": 24, + "id": 33, "name": "method1", "variant": "signature", "kind": 4096, @@ -352,14 +467,14 @@ "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 29, + "line": 42, "character": 4, - "url": "typedoc://inherit-param-doc.ts#L29" + "url": "typedoc://inherit-param-doc.ts#L42" } ], "parameters": [ { - "id": 25, + "id": 34, "name": "a", "variant": "param", "kind": 32768, @@ -378,7 +493,7 @@ } }, { - "id": 26, + "id": 35, "name": "c", "variant": "param", "kind": 32768, @@ -413,28 +528,96 @@ "target": 2, "name": "Base.method1" } + }, + { + "id": 36, + "name": "method2", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 43, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L43" + } + ], + "signatures": [ + { + "id": 37, + "name": "method2", + "variant": "signature", + "kind": 4096, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 43, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L43" + } + ], + "parameters": [ + { + "id": 38, + "name": "a", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Parameter A." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "number" + } + } + ], + "type": { + "type": "intrinsic", + "name": "void" + }, + "implementationOf": { + "type": "reference", + "target": 7, + "name": "Base.method2" + } + } + ], + "implementationOf": { + "type": "reference", + "target": 6, + "name": "Base.method2" + } } ], "groups": [ { "title": "Constructors", "children": [ - 21 + 30 ] }, { "title": "Methods", "children": [ - 23 + 32, + 36 ] } ], "sources": [ { "fileName": "inherit-param-doc.ts", - "line": 23, + "line": 36, "character": 13, - "url": "typedoc://inherit-param-doc.ts#L23" + "url": "typedoc://inherit-param-doc.ts#L36" } ], "implementedTypes": [ @@ -528,13 +711,90 @@ } } ] + }, + { + "id": 6, + "name": "method2", + "variant": "declaration", + "kind": 2048, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 11, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L11" + } + ], + "signatures": [ + { + "id": 7, + "name": "method2", + "variant": "signature", + "kind": 4096, + "flags": {}, + "sources": [ + { + "fileName": "inherit-param-doc.ts", + "line": 11, + "character": 4, + "url": "typedoc://inherit-param-doc.ts#L11" + } + ], + "parameters": [ + { + "id": 8, + "name": "a", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Parameter A." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "number" + } + }, + { + "id": 9, + "name": "b", + "variant": "param", + "kind": 32768, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Parameter B." + } + ] + }, + "type": { + "type": "intrinsic", + "name": "string" + } + } + ], + "type": { + "type": "intrinsic", + "name": "void" + } + } + ] } ], "groups": [ { "title": "Methods", "children": [ - 2 + 2, + 6 ] } ], @@ -549,17 +809,17 @@ "implementedBy": [ { "type": "reference", - "target": 6, + "target": 10, "name": "Class1" }, { "type": "reference", - "target": 13, + "target": 19, "name": "Class2" }, { "type": "reference", - "target": 20, + "target": 29, "name": "Class3" } ] @@ -569,9 +829,9 @@ { "title": "Classes", "children": [ - 6, - 13, - 20 + 10, + 19, + 29 ] }, { @@ -609,63 +869,119 @@ }, "6": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", - "qualifiedName": "Class1" + "qualifiedName": "Base.method2" + }, + "7": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Base.method2" + }, + "8": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "a" }, "9": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", - "qualifiedName": "Class1.method1" + "qualifiedName": "b" }, "10": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class1" + }, + "13": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class1.method1" + }, + "14": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class1.method1" }, - "11": { + "15": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "a" }, - "12": { + "16": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "b" }, - "13": { + "17": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class1.method2" + }, + "18": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class1.method2" + }, + "19": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class2" }, - "16": { + "22": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class2.method1" }, - "17": { + "23": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class2.method1" }, - "18": { + "24": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "a" }, - "19": { + "25": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "b" }, - "20": { + "26": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class2.method2" + }, + "27": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class2.method2" + }, + "28": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "a" + }, + "29": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class3" }, - "23": { + "32": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class3.method1" }, - "24": { + "33": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "Class3.method1" }, - "25": { + "34": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "a" }, - "26": { + "35": { "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", "qualifiedName": "c" + }, + "36": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class3.method2" + }, + "37": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "Class3.method2" + }, + "38": { + "sourceFileName": "src/test/converter/inherit-param-doc/inherit-param-doc.ts", + "qualifiedName": "a" + } + }, + "files": { + "entries": { + "1": "src/test/converter/inherit-param-doc/inherit-param-doc.ts" + }, + "reflections": { + "1": 0 } } } diff --git a/src/test/converter/inheritance/inherit-doc.ts b/src/test/converter/inheritance/inherit-doc.ts index 22394cc04..f901abddb 100644 --- a/src/test/converter/inheritance/inherit-doc.ts +++ b/src/test/converter/inheritance/inherit-doc.ts @@ -46,9 +46,9 @@ export interface InterfaceTarget<T> { * * Remarks will be inherited * - * @customBlock + * @default * - * This part of the commentary will not be inherited + * This part of the commentary will not be inherited (this is an abuse of this tag) * * @typeParam T - Type of arguments * @param arg1 - First argument diff --git a/src/test/converter/inheritance/specs.json b/src/test/converter/inheritance/specs.json index f61997853..3370f5e10 100644 --- a/src/test/converter/inheritance/specs.json +++ b/src/test/converter/inheritance/specs.json @@ -359,11 +359,11 @@ ] }, { - "tag": "@customBlock", + "tag": "@default", "content": [ { - "kind": "text", - "text": "This part of the commentary will not be inherited" + "kind": "code", + "text": "```ts\nThis part of the commentary will not be inherited (this is an abuse of this tag)\n```" } ] }, @@ -386,7 +386,7 @@ "url": "typedoc://inherit-doc.ts#L58" } ], - "typeParameter": [ + "typeParameters": [ { "id": 4, "name": "T", @@ -531,7 +531,7 @@ "url": "typedoc://inherit-doc.ts#L78" } ], - "typeParameter": [ + "typeParameters": [ { "id": 9, "name": "T", @@ -694,7 +694,9 @@ "name": "instanceProp", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mergable-class.ts", @@ -719,7 +721,8 @@ "variant": "declaration", "kind": 1024, "flags": { - "isStatic": true + "isStatic": true, + "isInherited": true }, "sources": [ { @@ -1131,5 +1134,15 @@ "sourceFileName": "src/test/converter/inheritance/mergable-class.ts", "qualifiedName": "My.instanceProp" } + }, + "files": { + "entries": { + "1": "src/test/converter/inheritance/inherit-doc.ts", + "2": "src/test/converter/inheritance/mergable-class.ts" + }, + "reflections": { + "1": 1, + "2": 24 + } } } diff --git a/src/test/converter/interface/index-signature.ts b/src/test/converter/interface/index-signature.ts index b6fdaba3f..f73f81cb4 100644 --- a/src/test/converter/interface/index-signature.ts +++ b/src/test/converter/interface/index-signature.ts @@ -6,9 +6,10 @@ export interface NumIndex { [x: number]: 1; } -// This is broken... but here's a test for the broken behavior so we know when it is fixed. export interface BothIndex { + /** Number index */ [x: number]: 1; + /** String index */ [x: string]: 1 | 2; } diff --git a/src/test/converter/interface/specs.json b/src/test/converter/interface/specs.json index fdc8b8859..471b6f3c0 100644 --- a/src/test/converter/interface/specs.json +++ b/src/test/converter/interface/specs.json @@ -237,43 +237,102 @@ "sources": [ { "fileName": "index-signature.ts", - "line": 10, + "line": 9, "character": 17, - "url": "typedoc://index-signature.ts#L10" + "url": "typedoc://index-signature.ts#L9" } ], - "indexSignature": { - "id": 22, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 11, - "character": 4, - "url": "typedoc://index-signature.ts#L11" + "indexSignatures": [ + { + "id": 22, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Number index" + } + ] + }, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 11, + "character": 4, + "url": "typedoc://index-signature.ts#L11" + } + ], + "parameters": [ + { + "id": 23, + "name": "x", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "number" + } + } + ], + "type": { + "type": "literal", + "value": 1 } - ], - "parameters": [ - { - "id": 23, - "name": "x", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "intrinsic", - "name": "number" + }, + { + "id": 24, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "String index" + } + ] + }, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 13, + "character": 4, + "url": "typedoc://index-signature.ts#L13" } + ], + "parameters": [ + { + "id": 25, + "name": "x", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "string" + } + } + ], + "type": { + "type": "union", + "types": [ + { + "type": "literal", + "value": 1 + }, + { + "type": "literal", + "value": 2 + } + ] } - ], - "type": { - "type": "literal", - "value": 1 } - } + ] }, { "id": 18, @@ -289,38 +348,40 @@ "url": "typedoc://index-signature.ts#L5" } ], - "indexSignature": { - "id": 19, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 6, - "character": 4, - "url": "typedoc://index-signature.ts#L6" - } - ], - "parameters": [ - { - "id": 20, - "name": "x", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "intrinsic", - "name": "number" + "indexSignatures": [ + { + "id": 19, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 6, + "character": 4, + "url": "typedoc://index-signature.ts#L6" } + ], + "parameters": [ + { + "id": 20, + "name": "x", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "number" + } + } + ], + "type": { + "type": "literal", + "value": 1 } - ], - "type": { - "type": "literal", - "value": 1 } - } + ] }, { "id": 15, @@ -336,41 +397,43 @@ "url": "typedoc://index-signature.ts#L1" } ], - "indexSignature": { - "id": 16, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 2, - "character": 4, - "url": "typedoc://index-signature.ts#L2" - } - ], - "parameters": [ - { - "id": 17, - "name": "x", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "intrinsic", - "name": "string" + "indexSignatures": [ + { + "id": 16, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 2, + "character": 4, + "url": "typedoc://index-signature.ts#L2" } + ], + "parameters": [ + { + "id": 17, + "name": "x", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "string" + } + } + ], + "type": { + "type": "literal", + "value": 1 } - ], - "type": { - "type": "literal", - "value": 1 } - } + ] }, { - "id": 24, + "id": 26, "name": "TypeIndex", "variant": "declaration", "kind": 2097152, @@ -378,15 +441,15 @@ "sources": [ { "fileName": "index-signature.ts", - "line": 15, + "line": 16, "character": 12, - "url": "typedoc://index-signature.ts#L15" + "url": "typedoc://index-signature.ts#L16" } ], "type": { "type": "reflection", "declaration": { - "id": 25, + "id": 27, "name": "__type", "variant": "declaration", "kind": 65536, @@ -394,43 +457,45 @@ "sources": [ { "fileName": "index-signature.ts", - "line": 15, + "line": 16, "character": 24, - "url": "typedoc://index-signature.ts#L15" + "url": "typedoc://index-signature.ts#L16" } ], - "indexSignature": { - "id": 26, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 15, - "character": 26, - "url": "typedoc://index-signature.ts#L15" - } - ], - "parameters": [ - { - "id": 27, - "name": "x", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "intrinsic", - "name": "string" + "indexSignatures": [ + { + "id": 28, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 16, + "character": 26, + "url": "typedoc://index-signature.ts#L16" } + ], + "parameters": [ + { + "id": 29, + "name": "x", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "string" + } + } + ], + "type": { + "type": "literal", + "value": 1 } - ], - "type": { - "type": "literal", - "value": 1 } - } + ] } } } @@ -447,7 +512,7 @@ { "title": "Type Aliases", "children": [ - 24 + 26 ] } ], @@ -461,14 +526,14 @@ ] }, { - "id": 28, + "id": 30, "name": "interface-empty", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 30, + "id": 32, "name": "ClassImplementingEmptyInterface", "variant": "declaration", "kind": 128, @@ -483,21 +548,21 @@ }, "children": [ { - "id": 31, + "id": 33, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 32, + "id": 34, "name": "new ClassImplementingEmptyInterface", "variant": "signature", "kind": 16384, "flags": {}, "type": { "type": "reference", - "target": 30, + "target": 32, "name": "ClassImplementingEmptyInterface", "package": "typedoc" } @@ -505,7 +570,7 @@ ] }, { - "id": 33, + "id": 35, "name": "name", "variant": "declaration", "kind": 1024, @@ -526,7 +591,7 @@ } }, { - "id": 34, + "id": 36, "name": "goto", "variant": "declaration", "kind": 2048, @@ -543,7 +608,7 @@ ], "signatures": [ { - "id": 35, + "id": 37, "name": "goto", "variant": "signature", "kind": 4096, @@ -568,19 +633,19 @@ { "title": "Constructors", "children": [ - 31 + 33 ] }, { "title": "Properties", "children": [ - 33 + 35 ] }, { "title": "Methods", "children": [ - 34 + 36 ] } ], @@ -595,14 +660,14 @@ "implementedTypes": [ { "type": "reference", - "target": 29, + "target": 31, "name": "EmptyInterface", "package": "typedoc" } ] }, { - "id": 29, + "id": 31, "name": "EmptyInterface", "variant": "declaration", "kind": 256, @@ -626,7 +691,7 @@ "implementedBy": [ { "type": "reference", - "target": 30, + "target": 32, "name": "ClassImplementingEmptyInterface" } ] @@ -636,13 +701,13 @@ { "title": "Classes", "children": [ - 30 + 32 ] }, { "title": "Interfaces", "children": [ - 29 + 31 ] } ], @@ -656,21 +721,21 @@ ] }, { - "id": 36, + "id": 38, "name": "interface-implementation", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 37, + "id": 39, "name": "Forms", "variant": "declaration", "kind": 4, "flags": {}, "children": [ { - "id": 81, + "id": 83, "name": "EventDispatcher", "variant": "declaration", "kind": 128, @@ -696,21 +761,21 @@ }, "children": [ { - "id": 82, + "id": 84, "name": "constructor", "variant": "declaration", "kind": 512, "flags": {}, "signatures": [ { - "id": 83, + "id": 85, "name": "new EventDispatcher", "variant": "signature", "kind": 16384, "flags": {}, - "typeParameter": [ + "typeParameters": [ { - "id": 84, + "id": 86, "name": "T", "variant": "typeParam", "kind": 131072, @@ -719,11 +784,11 @@ ], "type": { "type": "reference", - "target": 81, + "target": 83, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -738,7 +803,7 @@ ] }, { - "id": 85, + "id": 87, "name": "subscriptions", "variant": "declaration", "kind": 1024, @@ -757,11 +822,11 @@ "type": "array", "elementType": { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -775,7 +840,7 @@ } }, { - "id": 86, + "id": 88, "name": "add", "variant": "declaration", "kind": 2048, @@ -790,7 +855,7 @@ ], "signatures": [ { - "id": 87, + "id": 89, "name": "add", "variant": "signature", "kind": 4096, @@ -805,18 +870,18 @@ ], "parameters": [ { - "id": 88, + "id": 90, "name": "listener", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 38, + "target": 40, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -829,7 +894,7 @@ } }, { - "id": 89, + "id": 91, "name": "filter", "variant": "param", "kind": 32768, @@ -841,7 +906,7 @@ "defaultValue": "null" }, { - "id": 90, + "id": 92, "name": "priority", "variant": "param", "kind": 32768, @@ -855,11 +920,11 @@ ], "type": { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -872,19 +937,19 @@ }, "implementationOf": { "type": "reference", - "target": 66, + "target": 68, "name": "EventDispatcherInt.add" } } ], "implementationOf": { "type": "reference", - "target": 65, + "target": 67, "name": "EventDispatcherInt.add" } }, { - "id": 97, + "id": 99, "name": "clear", "variant": "declaration", "kind": 2048, @@ -899,7 +964,7 @@ ], "signatures": [ { - "id": 98, + "id": 100, "name": "clear", "variant": "signature", "kind": 4096, @@ -918,19 +983,19 @@ }, "implementationOf": { "type": "reference", - "target": 77, + "target": 79, "name": "EventDispatcherInt.clear" } } ], "implementationOf": { "type": "reference", - "target": 76, + "target": 78, "name": "EventDispatcherInt.clear" } }, { - "id": 94, + "id": 96, "name": "dispatch", "variant": "declaration", "kind": 2048, @@ -945,7 +1010,7 @@ ], "signatures": [ { - "id": 95, + "id": 97, "name": "dispatch", "variant": "signature", "kind": 4096, @@ -960,14 +1025,14 @@ ], "parameters": [ { - "id": 96, + "id": 98, "name": "event", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -981,19 +1046,19 @@ }, "implementationOf": { "type": "reference", - "target": 74, + "target": 76, "name": "EventDispatcherInt.dispatch" } } ], "implementationOf": { "type": "reference", - "target": 73, + "target": 75, "name": "EventDispatcherInt.dispatch" } }, { - "id": 99, + "id": 101, "name": "hasListeners", "variant": "declaration", "kind": 2048, @@ -1008,7 +1073,7 @@ ], "signatures": [ { - "id": 100, + "id": 102, "name": "hasListeners", "variant": "signature", "kind": 4096, @@ -1027,19 +1092,19 @@ }, "implementationOf": { "type": "reference", - "target": 79, + "target": 81, "name": "EventDispatcherInt.hasListeners" } } ], "implementationOf": { "type": "reference", - "target": 78, + "target": 80, "name": "EventDispatcherInt.hasListeners" } }, { - "id": 91, + "id": 93, "name": "remove", "variant": "declaration", "kind": 2048, @@ -1054,7 +1119,7 @@ ], "signatures": [ { - "id": 92, + "id": 94, "name": "remove", "variant": "signature", "kind": 4096, @@ -1069,18 +1134,18 @@ ], "parameters": [ { - "id": 93, + "id": 95, "name": "subscription", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -1099,14 +1164,14 @@ }, "implementationOf": { "type": "reference", - "target": 71, + "target": 73, "name": "EventDispatcherInt.remove" } } ], "implementationOf": { "type": "reference", - "target": 70, + "target": 72, "name": "EventDispatcherInt.remove" } } @@ -1115,23 +1180,23 @@ { "title": "Constructors", "children": [ - 82 + 84 ] }, { "title": "Properties", "children": [ - 85 + 87 ] }, { "title": "Methods", "children": [ - 86, - 97, - 94, + 88, 99, - 91 + 96, + 101, + 93 ] } ], @@ -1145,7 +1210,7 @@ ], "typeParameters": [ { - "id": 101, + "id": 103, "name": "T", "variant": "typeParam", "kind": 131072, @@ -1155,11 +1220,11 @@ "implementedTypes": [ { "type": "reference", - "target": 64, + "target": 66, "typeArguments": [ { "type": "reference", - "target": 84, + "target": 86, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventDispatcher.T", @@ -1173,7 +1238,7 @@ ] }, { - "id": 49, + "id": 51, "name": "Subscription", "variant": "declaration", "kind": 128, @@ -1188,7 +1253,7 @@ }, "children": [ { - "id": 50, + "id": 52, "name": "constructor", "variant": "declaration", "kind": 512, @@ -1203,7 +1268,7 @@ ], "signatures": [ { - "id": 51, + "id": 53, "name": "new Subscription", "variant": "signature", "kind": 16384, @@ -1216,9 +1281,9 @@ "url": "typedoc://interface-implementation.ts#L25" } ], - "typeParameter": [ + "typeParameters": [ { - "id": 52, + "id": 54, "name": "V", "variant": "typeParam", "kind": 131072, @@ -1227,18 +1292,18 @@ ], "parameters": [ { - "id": 53, + "id": 55, "name": "listener", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 38, + "target": 40, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1251,7 +1316,7 @@ } }, { - "id": 54, + "id": 56, "name": "filter", "variant": "param", "kind": 32768, @@ -1262,7 +1327,7 @@ } }, { - "id": 55, + "id": 57, "name": "priority", "variant": "param", "kind": 32768, @@ -1273,18 +1338,18 @@ } }, { - "id": 56, + "id": 58, "name": "dispatcher", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 81, + "target": 83, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1299,11 +1364,11 @@ ], "type": { "type": "reference", - "target": 49, + "target": 51, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1318,7 +1383,7 @@ ] }, { - "id": 60, + "id": 62, "name": "dispatcher", "variant": "declaration", "kind": 1024, @@ -1335,11 +1400,11 @@ ], "type": { "type": "reference", - "target": 81, + "target": 83, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1352,7 +1417,7 @@ } }, { - "id": 58, + "id": 60, "name": "filter", "variant": "declaration", "kind": 1024, @@ -1373,12 +1438,12 @@ }, "implementationOf": { "type": "reference", - "target": 45, + "target": 47, "name": "SubscriptionInt.filter" } }, { - "id": 57, + "id": 59, "name": "listener", "variant": "declaration", "kind": 1024, @@ -1395,11 +1460,11 @@ ], "type": { "type": "reference", - "target": 38, + "target": 40, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1412,12 +1477,12 @@ }, "implementationOf": { "type": "reference", - "target": 43, + "target": 45, "name": "SubscriptionInt.listener" } }, { - "id": 59, + "id": 61, "name": "priority", "variant": "declaration", "kind": 1024, @@ -1438,12 +1503,12 @@ }, "implementationOf": { "type": "reference", - "target": 44, + "target": 46, "name": "SubscriptionInt.priority" } }, { - "id": 61, + "id": 63, "name": "unsubscribe", "variant": "declaration", "kind": 2048, @@ -1458,7 +1523,7 @@ ], "signatures": [ { - "id": 62, + "id": 64, "name": "unsubscribe", "variant": "signature", "kind": 4096, @@ -1485,14 +1550,14 @@ }, "implementationOf": { "type": "reference", - "target": 47, + "target": 49, "name": "SubscriptionInt.unsubscribe" } } ], "implementationOf": { "type": "reference", - "target": 46, + "target": 48, "name": "SubscriptionInt.unsubscribe" } } @@ -1501,22 +1566,22 @@ { "title": "Constructors", "children": [ - 50 + 52 ] }, { "title": "Properties", "children": [ + 62, 60, - 58, - 57, - 59 + 59, + 61 ] }, { "title": "Methods", "children": [ - 61 + 63 ] } ], @@ -1530,7 +1595,7 @@ ], "typeParameters": [ { - "id": 63, + "id": 65, "name": "V", "variant": "typeParam", "kind": 131072, @@ -1540,11 +1605,11 @@ "implementedTypes": [ { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 52, + "target": 54, "name": "V", "package": "typedoc", "qualifiedName": "Forms.Subscription.V", @@ -1558,7 +1623,7 @@ ] }, { - "id": 64, + "id": 66, "name": "EventDispatcherInt", "variant": "declaration", "kind": 256, @@ -1573,7 +1638,7 @@ }, "children": [ { - "id": 65, + "id": 67, "name": "add", "variant": "declaration", "kind": 2048, @@ -1588,7 +1653,7 @@ ], "signatures": [ { - "id": 66, + "id": 68, "name": "add", "variant": "signature", "kind": 4096, @@ -1603,18 +1668,18 @@ ], "parameters": [ { - "id": 67, + "id": 69, "name": "listener", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 38, + "target": 40, "typeArguments": [ { "type": "reference", - "target": 80, + "target": 82, "name": "U", "package": "typedoc", "qualifiedName": "Forms.EventDispatcherInt.U", @@ -1627,7 +1692,7 @@ } }, { - "id": 68, + "id": 70, "name": "filter", "variant": "param", "kind": 32768, @@ -1640,7 +1705,7 @@ } }, { - "id": 69, + "id": 71, "name": "priority", "variant": "param", "kind": 32768, @@ -1655,11 +1720,11 @@ ], "type": { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 80, + "target": 82, "name": "U", "package": "typedoc", "qualifiedName": "Forms.EventDispatcherInt.U", @@ -1674,7 +1739,7 @@ ] }, { - "id": 76, + "id": 78, "name": "clear", "variant": "declaration", "kind": 2048, @@ -1689,7 +1754,7 @@ ], "signatures": [ { - "id": 77, + "id": 79, "name": "clear", "variant": "signature", "kind": 4096, @@ -1710,7 +1775,7 @@ ] }, { - "id": 73, + "id": 75, "name": "dispatch", "variant": "declaration", "kind": 2048, @@ -1725,7 +1790,7 @@ ], "signatures": [ { - "id": 74, + "id": 76, "name": "dispatch", "variant": "signature", "kind": 4096, @@ -1740,14 +1805,14 @@ ], "parameters": [ { - "id": 75, + "id": 77, "name": "parameter", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 80, + "target": 82, "name": "U", "package": "typedoc", "qualifiedName": "Forms.EventDispatcherInt.U", @@ -1763,7 +1828,7 @@ ] }, { - "id": 78, + "id": 80, "name": "hasListeners", "variant": "declaration", "kind": 2048, @@ -1778,7 +1843,7 @@ ], "signatures": [ { - "id": 79, + "id": 81, "name": "hasListeners", "variant": "signature", "kind": 4096, @@ -1799,7 +1864,7 @@ ] }, { - "id": 70, + "id": 72, "name": "remove", "variant": "declaration", "kind": 2048, @@ -1814,7 +1879,7 @@ ], "signatures": [ { - "id": 71, + "id": 73, "name": "remove", "variant": "signature", "kind": 4096, @@ -1829,18 +1894,18 @@ ], "parameters": [ { - "id": 72, + "id": 74, "name": "subscription", "variant": "param", "kind": 32768, "flags": {}, "type": { "type": "reference", - "target": 42, + "target": 44, "typeArguments": [ { "type": "reference", - "target": 80, + "target": 82, "name": "U", "package": "typedoc", "qualifiedName": "Forms.EventDispatcherInt.U", @@ -1865,11 +1930,11 @@ { "title": "Methods", "children": [ - 65, - 76, - 73, + 67, 78, - 70 + 75, + 80, + 72 ] } ], @@ -1883,7 +1948,7 @@ ], "typeParameters": [ { - "id": 80, + "id": 82, "name": "U", "variant": "typeParam", "kind": 131072, @@ -1893,13 +1958,13 @@ "implementedBy": [ { "type": "reference", - "target": 81, + "target": 83, "name": "EventDispatcher" } ] }, { - "id": 38, + "id": 40, "name": "EventListener", "variant": "declaration", "kind": 256, @@ -1922,7 +1987,7 @@ ], "typeParameters": [ { - "id": 39, + "id": 41, "name": "T", "variant": "typeParam", "kind": 131072, @@ -1931,7 +1996,7 @@ ], "signatures": [ { - "id": 40, + "id": 42, "name": "EventListener", "variant": "signature", "kind": 4096, @@ -1946,7 +2011,7 @@ ], "parameters": [ { - "id": 41, + "id": 43, "name": "parameter", "variant": "param", "kind": 32768, @@ -1961,7 +2026,7 @@ }, "type": { "type": "reference", - "target": 39, + "target": 41, "name": "T", "package": "typedoc", "qualifiedName": "Forms.EventListener.T", @@ -1977,7 +2042,7 @@ ] }, { - "id": 42, + "id": 44, "name": "SubscriptionInt", "variant": "declaration", "kind": 256, @@ -1992,7 +2057,7 @@ }, "children": [ { - "id": 45, + "id": 47, "name": "filter", "variant": "declaration", "kind": 1024, @@ -2011,7 +2076,7 @@ } }, { - "id": 43, + "id": 45, "name": "listener", "variant": "declaration", "kind": 1024, @@ -2026,11 +2091,11 @@ ], "type": { "type": "reference", - "target": 38, + "target": 40, "typeArguments": [ { "type": "reference", - "target": 48, + "target": 50, "name": "T", "package": "typedoc", "qualifiedName": "Forms.SubscriptionInt.T", @@ -2043,7 +2108,7 @@ } }, { - "id": 44, + "id": 46, "name": "priority", "variant": "declaration", "kind": 1024, @@ -2062,7 +2127,7 @@ } }, { - "id": 46, + "id": 48, "name": "unsubscribe", "variant": "declaration", "kind": 2048, @@ -2077,7 +2142,7 @@ ], "signatures": [ { - "id": 47, + "id": 49, "name": "unsubscribe", "variant": "signature", "kind": 4096, @@ -2110,15 +2175,15 @@ { "title": "Properties", "children": [ + 47, 45, - 43, - 44 + 46 ] }, { "title": "Methods", "children": [ - 46 + 48 ] } ], @@ -2132,7 +2197,7 @@ ], "typeParameters": [ { - "id": 48, + "id": 50, "name": "T", "variant": "typeParam", "kind": 131072, @@ -2142,7 +2207,7 @@ "implementedBy": [ { "type": "reference", - "target": 49, + "target": 51, "name": "Subscription" } ] @@ -2152,16 +2217,16 @@ { "title": "Classes", "children": [ - 81, - 49 + 83, + 51 ] }, { "title": "Interfaces", "children": [ - 64, - 38, - 42 + 66, + 40, + 44 ] } ], @@ -2179,7 +2244,7 @@ { "title": "Namespaces", "children": [ - 37 + 39 ] } ], @@ -2193,21 +2258,21 @@ ] }, { - "id": 102, + "id": 104, "name": "merging", "variant": "declaration", "kind": 2, "flags": {}, "children": [ { - "id": 103, + "id": 105, "name": "Base", "variant": "declaration", "kind": 256, "flags": {}, "children": [ { - "id": 104, + "id": 106, "name": "base", "variant": "declaration", "kind": 1024, @@ -2230,7 +2295,7 @@ { "title": "Properties", "children": [ - 104 + 106 ] } ], @@ -2245,25 +2310,25 @@ "extendedBy": [ { "type": "reference", - "target": 107, + "target": 109, "name": "Child" }, { "type": "reference", - "target": 112, + "target": 114, "name": "Child2" } ] }, { - "id": 105, + "id": 107, "name": "Base2", "variant": "declaration", "kind": 256, "flags": {}, "children": [ { - "id": 106, + "id": 108, "name": "base2", "variant": "declaration", "kind": 1024, @@ -2286,7 +2351,7 @@ { "title": "Properties", "children": [ - 106 + 108 ] } ], @@ -2301,24 +2366,26 @@ "extendedBy": [ { "type": "reference", - "target": 107, + "target": 109, "name": "Child" } ] }, { - "id": 107, + "id": 109, "name": "Child", "variant": "declaration", "kind": 256, "flags": {}, "children": [ { - "id": 110, + "id": 112, "name": "base", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2333,16 +2400,18 @@ }, "inheritedFrom": { "type": "reference", - "target": 104, + "target": 106, "name": "Base.base" } }, { - "id": 111, + "id": 113, "name": "base2", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2357,12 +2426,12 @@ }, "inheritedFrom": { "type": "reference", - "target": 106, + "target": 108, "name": "Base2.base2" } }, { - "id": 108, + "id": 110, "name": "child", "variant": "declaration", "kind": 1024, @@ -2381,7 +2450,7 @@ } }, { - "id": 109, + "id": 111, "name": "child3", "variant": "declaration", "kind": 1024, @@ -2404,10 +2473,10 @@ { "title": "Properties", "children": [ + 112, + 113, 110, - 111, - 108, - 109 + 111 ] } ], @@ -2428,13 +2497,13 @@ "extendedTypes": [ { "type": "reference", - "target": 103, + "target": 105, "name": "Base", "package": "typedoc" }, { "type": "reference", - "target": 105, + "target": 107, "name": "Base2", "package": "typedoc" } @@ -2442,24 +2511,26 @@ "extendedBy": [ { "type": "reference", - "target": 112, + "target": 114, "name": "Child2" } ] }, { - "id": 112, + "id": 114, "name": "Child2", "variant": "declaration", "kind": 256, "flags": {}, "children": [ { - "id": 116, + "id": 118, "name": "base", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2474,16 +2545,18 @@ }, "inheritedFrom": { "type": "reference", - "target": 104, + "target": 106, "name": "Base.base" } }, { - "id": 117, + "id": 119, "name": "base2", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2498,16 +2571,18 @@ }, "inheritedFrom": { "type": "reference", - "target": 111, + "target": 113, "name": "Child.base2" } }, { - "id": 114, + "id": 116, "name": "child", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2522,12 +2597,12 @@ }, "inheritedFrom": { "type": "reference", - "target": 108, + "target": 110, "name": "Child.child" } }, { - "id": 113, + "id": 115, "name": "child2", "variant": "declaration", "kind": 1024, @@ -2546,11 +2621,13 @@ } }, { - "id": 115, + "id": 117, "name": "child3", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "merging.ts", @@ -2565,7 +2642,7 @@ }, "inheritedFrom": { "type": "reference", - "target": 109, + "target": 111, "name": "Child.child3" } } @@ -2574,11 +2651,11 @@ { "title": "Properties", "children": [ + 118, + 119, 116, - 117, - 114, - 113, - 115 + 115, + 117 ] } ], @@ -2593,13 +2670,13 @@ "extendedTypes": [ { "type": "reference", - "target": 107, + "target": 109, "name": "Child", "package": "typedoc" }, { "type": "reference", - "target": 103, + "target": 105, "name": "Base", "package": "typedoc" } @@ -2610,10 +2687,10 @@ { "title": "Interfaces", "children": [ - 103, 105, 107, - 112 + 109, + 114 ] } ], @@ -2633,9 +2710,9 @@ "children": [ 1, 14, - 28, - 36, - 102 + 30, + 38, + 104 ] } ], @@ -2722,360 +2799,380 @@ "qualifiedName": "BothIndex.__index" }, "24": { + "sourceFileName": "src/test/converter/interface/index-signature.ts", + "qualifiedName": "BothIndex.__index" + }, + "26": { "sourceFileName": "src/test/converter/interface/index-signature.ts", "qualifiedName": "TypeIndex" }, - "25": { + "27": { "sourceFileName": "src/test/converter/interface/index-signature.ts", "qualifiedName": "__type" }, - "26": { + "28": { "sourceFileName": "src/test/converter/interface/index-signature.ts", "qualifiedName": "__type.__index" }, - "28": { + "30": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "" }, - "29": { + "31": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "EmptyInterface" }, - "30": { + "32": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "ClassImplementingEmptyInterface" }, - "33": { + "35": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "ClassImplementingEmptyInterface.name" }, - "34": { + "36": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "ClassImplementingEmptyInterface.goto" }, - "35": { + "37": { "sourceFileName": "src/test/converter/interface/interface-empty.ts", "qualifiedName": "ClassImplementingEmptyInterface.goto" }, - "36": { + "38": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "" }, - "37": { + "39": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms" }, - "38": { + "40": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventListener" }, - "39": { + "41": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventListener.T" }, - "40": { + "42": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventListener" }, - "41": { + "43": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "parameter" }, - "42": { + "44": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt" }, - "43": { + "45": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.listener" }, - "44": { + "46": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.priority" }, - "45": { + "47": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.filter" }, - "46": { + "48": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.unsubscribe" }, - "47": { + "49": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.unsubscribe" }, - "48": { + "50": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.SubscriptionInt.T" }, - "49": { + "51": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription" }, - "50": { + "52": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.__constructor" }, - "51": { + "53": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription" }, - "52": { + "54": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.V" }, - "53": { + "55": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "listener" }, - "54": { + "56": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "filter" }, - "55": { + "57": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "priority" }, - "56": { + "58": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "dispatcher" }, - "57": { + "59": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.listener" }, - "58": { + "60": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.filter" }, - "59": { + "61": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.priority" }, - "60": { + "62": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.dispatcher" }, - "61": { + "63": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.unsubscribe" }, - "62": { + "64": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.unsubscribe" }, - "63": { + "65": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.Subscription.V" }, - "64": { + "66": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt" }, - "65": { + "67": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.add" }, - "66": { + "68": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.add" }, - "67": { + "69": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "listener" }, - "68": { + "70": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "filter" }, - "69": { + "71": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "priority" }, - "70": { + "72": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.remove" }, - "71": { + "73": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.remove" }, - "72": { + "74": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "subscription" }, - "73": { + "75": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.dispatch" }, - "74": { + "76": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.dispatch" }, - "75": { + "77": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "parameter" }, - "76": { + "78": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.clear" }, - "77": { + "79": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.clear" }, - "78": { + "80": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.hasListeners" }, - "79": { + "81": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.hasListeners" }, - "80": { + "82": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcherInt.U" }, - "81": { + "83": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher" }, - "84": { + "86": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.T" }, - "85": { + "87": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.subscriptions" }, - "86": { + "88": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.add" }, - "87": { + "89": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.add" }, - "88": { + "90": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "listener" }, - "89": { + "91": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "filter" }, - "90": { + "92": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "priority" }, - "91": { + "93": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.remove" }, - "92": { + "94": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.remove" }, - "93": { + "95": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "subscription" }, - "94": { + "96": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.dispatch" }, - "95": { + "97": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.dispatch" }, - "96": { + "98": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "event" }, - "97": { + "99": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.clear" }, - "98": { + "100": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.clear" }, - "99": { + "101": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.hasListeners" }, - "100": { + "102": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.hasListeners" }, - "101": { + "103": { "sourceFileName": "src/test/converter/interface/interface-implementation.ts", "qualifiedName": "Forms.EventDispatcher.T" }, - "102": { + "104": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "" }, - "103": { + "105": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base" }, - "104": { + "106": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base.base" }, - "105": { + "107": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base2" }, - "106": { + "108": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base2.base2" }, - "107": { + "109": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child" }, - "108": { + "110": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child.child" }, - "109": { + "111": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child.child3" }, - "110": { + "112": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base.base" }, - "111": { + "113": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base2.base2" }, - "112": { + "114": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child2" }, - "113": { + "115": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child2.child2" }, - "114": { + "116": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child.child" }, - "115": { + "117": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Child.child3" }, - "116": { + "118": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base.base" }, - "117": { + "119": { "sourceFileName": "src/test/converter/interface/merging.ts", "qualifiedName": "Base2.base2" } + }, + "files": { + "entries": { + "1": "src/test/converter/interface/constructor-type.ts", + "2": "src/test/converter/interface/index-signature.ts", + "3": "src/test/converter/interface/interface-empty.ts", + "4": "src/test/converter/interface/interface-implementation.ts", + "5": "src/test/converter/interface/merging.ts" + }, + "reflections": { + "1": 1, + "2": 14, + "3": 30, + "4": 38, + "5": 104 + } } } diff --git a/src/test/converter/js/export-eq-type.js b/src/test/converter/js/export-eq-type.js index 3bf3ea400..feeddbe07 100644 --- a/src/test/converter/js/export-eq-type.js +++ b/src/test/converter/js/export-eq-type.js @@ -1,6 +1,6 @@ /** @typedef {string} Foo */ -/** @param {Foo} x */ +/** @param {Foo} x x desc */ const foo = (x) => x; module.exports = foo; diff --git a/src/test/converter/js/index.js b/src/test/converter/js/index.js index a325b7569..172f95e6a 100644 --- a/src/test/converter/js/index.js +++ b/src/test/converter/js/index.js @@ -47,8 +47,8 @@ /** * @callback Foo - * @param {...string} args - * @returns {number} + * @param {...string} args Foo param description + * @returns {number} Foo return description */ /** @type {Foo} */ diff --git a/src/test/converter/js/specs.json b/src/test/converter/js/specs.json index a51677c34..88f393ac6 100644 --- a/src/test/converter/js/specs.json +++ b/src/test/converter/js/specs.json @@ -67,6 +67,14 @@ "variant": "param", "kind": 32768, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "x desc" + } + ] + }, "type": { "type": "intrinsic", "name": "string" @@ -375,6 +383,14 @@ "flags": { "isRest": true }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Foo param description" + } + ] + }, "type": { "type": "array", "elementType": { @@ -894,6 +910,20 @@ "variant": "signature", "kind": 4096, "flags": {}, + "comment": { + "summary": [], + "blockTags": [ + { + "tag": "@returns", + "content": [ + { + "kind": "text", + "text": "Foo return description" + } + ] + } + ] + }, "sources": [ { "fileName": "index.js", @@ -911,6 +941,14 @@ "flags": { "isRest": true }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Foo param description" + } + ] + }, "type": { "type": "array", "elementType": { @@ -1164,5 +1202,15 @@ "sourceFileName": "src/test/converter/js/index.js", "qualifiedName": "args" } + }, + "files": { + "entries": { + "1": "src/test/converter/js/export-eq-type.js", + "2": "src/test/converter/js/index.js" + }, + "reflections": { + "1": 1, + "2": 6 + } } } diff --git a/src/test/converter/mixin/specs.json b/src/test/converter/mixin/specs.json index c96ba825a..5ba85c2b5 100644 --- a/src/test/converter/mixin/specs.json +++ b/src/test/converter/mixin/specs.json @@ -180,7 +180,9 @@ "name": "baseProperty", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -225,7 +227,9 @@ "name": "property1", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -250,7 +254,9 @@ "name": "property2", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -275,7 +281,9 @@ "name": "baseMethod", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -290,7 +298,9 @@ "name": "baseMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -357,7 +367,9 @@ "name": "method1", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -372,7 +384,9 @@ "name": "method1", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -426,7 +440,9 @@ "name": "method2", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -441,7 +457,9 @@ "name": "method2", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -705,7 +723,9 @@ "name": "baseProperty", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -730,7 +750,9 @@ "name": "property1", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -755,7 +777,9 @@ "name": "baseMethod", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -770,7 +794,9 @@ "name": "baseMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -801,7 +827,9 @@ "name": "method1", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -816,7 +844,9 @@ "name": "method1", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -930,7 +960,9 @@ "name": "baseProperty", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -955,7 +987,9 @@ "name": "property1", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -980,7 +1014,9 @@ "name": "property2", "variant": "declaration", "kind": 1024, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1005,7 +1041,9 @@ "name": "baseMethod", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1020,7 +1058,9 @@ "name": "baseMethod", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1051,7 +1091,9 @@ "name": "method1", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1066,7 +1108,9 @@ "name": "method1", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1120,7 +1164,9 @@ "name": "method2", "variant": "declaration", "kind": 2048, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1135,7 +1181,9 @@ "name": "method2", "variant": "signature", "kind": 4096, - "flags": {}, + "flags": { + "isInherited": true + }, "sources": [ { "fileName": "mixin.ts", @@ -1532,6 +1580,14 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The \"mixin function\" of the Mixin1" + } + ] + }, "sources": [ { "fileName": "mixin.ts", @@ -1547,14 +1603,6 @@ "variant": "signature", "kind": 4096, "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "The \"mixin function\" of the Mixin1" - } - ] - }, "sources": [ { "fileName": "mixin.ts", @@ -1563,7 +1611,7 @@ "url": "typedoc://mixin.ts#L30" } ], - "typeParameter": [ + "typeParameters": [ { "id": 21, "name": "T", @@ -1720,6 +1768,14 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The \"mixin function\" of the Mixin2" + } + ] + }, "sources": [ { "fileName": "mixin.ts", @@ -1735,14 +1791,6 @@ "variant": "signature", "kind": 4096, "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "The \"mixin function\" of the Mixin2" - } - ] - }, "sources": [ { "fileName": "mixin.ts", @@ -1751,7 +1799,7 @@ "url": "typedoc://mixin.ts#L50" } ], - "typeParameter": [ + "typeParameters": [ { "id": 37, "name": "T", @@ -1919,6 +1967,14 @@ "variant": "declaration", "kind": 64, "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The \"mixin function\" of the Mixin3" + } + ] + }, "sources": [ { "fileName": "mixin.ts", @@ -1940,14 +1996,6 @@ "variant": "signature", "kind": 4096, "flags": {}, - "comment": { - "summary": [ - { - "kind": "text", - "text": "The \"mixin function\" of the Mixin3" - } - ] - }, "sources": [ { "fileName": "mixin.ts", @@ -1956,7 +2004,7 @@ "url": "typedoc://mixin.ts#L70" } ], - "typeParameter": [ + "typeParameters": [ { "id": 57, "name": "T", @@ -2465,5 +2513,13 @@ "sourceFileName": "src/test/converter/mixin/mixin.ts", "qualifiedName": "Base.baseMethod" } + }, + "files": { + "entries": { + "1": "src/test/converter/mixin/mixin.ts" + }, + "reflections": { + "1": 0 + } } } diff --git a/src/test/converter/react/specs.json b/src/test/converter/react/specs.json index 8fd237a69..c3225a456 100644 --- a/src/test/converter/react/specs.json +++ b/src/test/converter/react/specs.json @@ -277,5 +277,13 @@ "sourceFileName": "src/test/converter/react/react.tsx", "qualifiedName": "Demo.render" } + }, + "files": { + "entries": { + "1": "src/test/converter/react/react.tsx" + }, + "reflections": { + "1": 0 + } } } diff --git a/src/test/converter/types/specs.json b/src/test/converter/types/specs.json index c8428ec5e..066942332 100644 --- a/src/test/converter/types/specs.json +++ b/src/test/converter/types/specs.json @@ -344,47 +344,49 @@ "url": "typedoc://index-signature.ts#L5" } ], - "indexSignature": { - "id": 18, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 6, - "character": 4, - "url": "typedoc://index-signature.ts#L6" - } - ], - "parameters": [ - { - "id": 19, - "name": "optName", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "templateLiteral", - "head": "data-", - "tail": [ - [ - { - "type": "intrinsic", - "name": "string" - }, - "" + "indexSignatures": [ + { + "id": 18, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 6, + "character": 4, + "url": "typedoc://index-signature.ts#L6" + } + ], + "parameters": [ + { + "id": 19, + "name": "optName", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "templateLiteral", + "head": "data-", + "tail": [ + [ + { + "type": "intrinsic", + "name": "string" + }, + "" + ] ] - ] + } } + ], + "type": { + "type": "intrinsic", + "name": "unknown" } - ], - "type": { - "type": "intrinsic", - "name": "unknown" } - } + ] }, { "id": 14, @@ -400,38 +402,40 @@ "url": "typedoc://index-signature.ts#L1" } ], - "indexSignature": { - "id": 15, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 2, - "character": 4, - "url": "typedoc://index-signature.ts#L2" - } - ], - "parameters": [ - { - "id": 16, - "name": "sym", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "intrinsic", - "name": "symbol" + "indexSignatures": [ + { + "id": 15, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 2, + "character": 4, + "url": "typedoc://index-signature.ts#L2" + } + ], + "parameters": [ + { + "id": 16, + "name": "sym", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "intrinsic", + "name": "symbol" + } } + ], + "type": { + "type": "intrinsic", + "name": "unknown" } - ], - "type": { - "type": "intrinsic", - "name": "unknown" } - } + ] }, { "id": 20, @@ -447,47 +451,49 @@ "url": "typedoc://index-signature.ts#L9" } ], - "indexSignature": { - "id": 21, - "name": "__index", - "variant": "signature", - "kind": 8192, - "flags": {}, - "sources": [ - { - "fileName": "index-signature.ts", - "line": 10, - "character": 4, - "url": "typedoc://index-signature.ts#L10" - } - ], - "parameters": [ - { - "id": 22, - "name": "optName", - "variant": "param", - "kind": 32768, - "flags": {}, - "type": { - "type": "union", - "types": [ - { - "type": "intrinsic", - "name": "string" - }, - { - "type": "intrinsic", - "name": "symbol" - } - ] + "indexSignatures": [ + { + "id": 21, + "name": "__index", + "variant": "signature", + "kind": 8192, + "flags": {}, + "sources": [ + { + "fileName": "index-signature.ts", + "line": 10, + "character": 4, + "url": "typedoc://index-signature.ts#L10" + } + ], + "parameters": [ + { + "id": 22, + "name": "optName", + "variant": "param", + "kind": 32768, + "flags": {}, + "type": { + "type": "union", + "types": [ + { + "type": "intrinsic", + "name": "string" + }, + { + "type": "intrinsic", + "name": "symbol" + } + ] + } } + ], + "type": { + "type": "intrinsic", + "name": "unknown" } - ], - "type": { - "type": "intrinsic", - "name": "unknown" } - } + ] } ], "groups": [ @@ -704,7 +710,7 @@ "url": "typedoc://mapped.ts#L9" } ], - "typeParameter": [ + "typeParameters": [ { "id": 30, "name": "T", @@ -821,7 +827,7 @@ "url": "typedoc://mapped.ts#L1" } ], - "typeParameter": [ + "typeParameters": [ { "id": 26, "name": "T", @@ -2297,5 +2303,27 @@ "sourceFileName": "src/test/converter/types/union-or-intersection.ts", "qualifiedName": "ThirdType.thirdComplexProperty" } + }, + "files": { + "entries": { + "1": "src/test/converter/types/general.ts", + "2": "src/test/converter/types/index-signature.ts", + "3": "src/test/converter/types/mapped.ts", + "4": "src/test/converter/types/parens.ts", + "5": "src/test/converter/types/query.ts", + "6": "src/test/converter/types/tuple.ts", + "7": "src/test/converter/types/type-operator.ts", + "8": "src/test/converter/types/union-or-intersection.ts" + }, + "reflections": { + "1": 1, + "2": 13, + "3": 23, + "4": 36, + "5": 40, + "6": 43, + "7": 54, + "8": 62 + } } } diff --git a/src/test/converter/variables/specs.json b/src/test/converter/variables/specs.json index cc2390a04..b0d5a47fc 100644 --- a/src/test/converter/variables/specs.json +++ b/src/test/converter/variables/specs.json @@ -40,7 +40,7 @@ "variant": "signature", "kind": 16384, "flags": {}, - "typeParameter": [ + "typeParameters": [ { "id": 5, "name": "T", @@ -2696,5 +2696,19 @@ "sourceFileName": "src/test/converter/variables/variable.ts", "qualifiedName": "__object.b" } + }, + "files": { + "entries": { + "1": "src/test/converter/variables/array.ts", + "2": "src/test/converter/variables/destructuring.ts", + "3": "src/test/converter/variables/literal.ts", + "4": "src/test/converter/variables/variable.ts" + }, + "reflections": { + "1": 1, + "2": 15, + "3": 34, + "4": 96 + } } } diff --git a/src/test/converter/variables/specs.nodoc.json b/src/test/converter/variables/specs.nodoc.json index 83567507f..2b7a85989 100644 --- a/src/test/converter/variables/specs.nodoc.json +++ b/src/test/converter/variables/specs.nodoc.json @@ -1528,5 +1528,18 @@ "sourceFileName": "src/test/converter/variables/literal.ts", "qualifiedName": "__object.literal2" } + }, + "files": { + "entries": { + "1": "src/test/converter/variables/array.ts", + "2": "src/test/converter/variables/destructuring.ts", + "3": "src/test/converter/variables/literal.ts", + "4": "src/test/converter/variables/variable.ts" + }, + "reflections": { + "1": 1, + "2": 15, + "3": 34 + } } } diff --git a/src/test/converter2/behavior/cascadedModifiers.ts b/src/test/converter2/behavior/cascadedModifiers.ts new file mode 100644 index 000000000..a5730a522 --- /dev/null +++ b/src/test/converter2/behavior/cascadedModifiers.ts @@ -0,0 +1,14 @@ +/** + * @beta + */ +export namespace BetaStuff { + export class AlsoBeta { + betaFish() {} + + /** @alpha */ + alphaFish() {} + } +} + +/** @alpha @beta */ +export const mutuallyExclusive = true; diff --git a/src/test/converter2/behavior/externalSymbols.ts b/src/test/converter2/behavior/externalSymbols.ts index f3a7d93c9..05625ed79 100644 --- a/src/test/converter2/behavior/externalSymbols.ts +++ b/src/test/converter2/behavior/externalSymbols.ts @@ -1,4 +1,4 @@ -import { Lexer, Slugger } from "marked"; +import { Token, ParseLinkTitleResult } from "markdown-it"; /** * Testing custom external link resolution @@ -6,5 +6,5 @@ import { Lexer, Slugger } from "marked"; */ export type P = Promise<string>; -export const L = new Lexer(); -export const S = new Slugger(); +export declare const T: Token; +export declare const Pr: ParseLinkTitleResult; diff --git a/src/test/converter2/behavior/hideconstructor.ts b/src/test/converter2/behavior/hideconstructor.ts new file mode 100644 index 000000000..fb65d0370 --- /dev/null +++ b/src/test/converter2/behavior/hideconstructor.ts @@ -0,0 +1,14 @@ +// https://github.com/TypeStrong/typedoc/issues/2577 + +/** @hideconstructor */ +export class StaticOnly { + static foo() {} + + /** @hideconstructor */ + notHidden = true; +} + +export class IgnoredCtor { + /** @hideconstructor */ + constructor() {} +} diff --git a/src/test/converter2/behavior/inferredPredicates.ts b/src/test/converter2/behavior/inferredPredicates.ts new file mode 100644 index 000000000..f24f43836 --- /dev/null +++ b/src/test/converter2/behavior/inferredPredicates.ts @@ -0,0 +1,3 @@ +export const isNumber = (x: unknown) => typeof x === "number"; + +export const isNonNullish = <T>(x: T) => x != null; diff --git a/src/test/converter2/behavior/refusingToRecurse.ts b/src/test/converter2/behavior/refusingToRecurse.ts index 6eb6719d0..4afa5bee7 100644 --- a/src/test/converter2/behavior/refusingToRecurse.ts +++ b/src/test/converter2/behavior/refusingToRecurse.ts @@ -5,14 +5,14 @@ type OptionalKeys<T> = { type FromSchema<T> = T extends typeof String ? string : T extends readonly [typeof Array, infer U] - ? FromSchema<U>[] - : { - [K in OptionalKeys<T>]?: FromSchema< - (T[K] & { $opt: unknown })["$opt"] - >; - } & { - [K in Exclude<keyof T, OptionalKeys<T>>]: FromSchema<T[K]>; - }; + ? FromSchema<U>[] + : { + [K in OptionalKeys<T>]?: FromSchema< + (T[K] & { $opt: unknown })["$opt"] + >; + } & { + [K in Exclude<keyof T, OptionalKeys<T>>]: FromSchema<T[K]>; + }; export const schema = { x: [ diff --git a/src/test/converter2/behavior/resolutionMode.ts b/src/test/converter2/behavior/resolutionMode.ts index e4e1db574..601055ec9 100644 --- a/src/test/converter2/behavior/resolutionMode.ts +++ b/src/test/converter2/behavior/resolutionMode.ts @@ -1,11 +1,7 @@ // Resolve `pkg` as if we were importing with a `require()` -import type { TypeFromRequire } from "dual" with { - "resolution-mode": "require" -}; +import type { TypeFromRequire } from "dual" with { "resolution-mode": "require" }; // Resolve `pkg` as if we were importing with an `import` -import type { TypeFromImport } from "dual" with { - "resolution-mode": "import" -}; +import type { TypeFromImport } from "dual" with { "resolution-mode": "import" }; export interface MergedType extends TypeFromRequire, TypeFromImport {} diff --git a/src/test/converter2/issues/gh2521.d.ts b/src/test/converter2/issues/gh2521.d.ts new file mode 100644 index 000000000..253cd0dee --- /dev/null +++ b/src/test/converter2/issues/gh2521.d.ts @@ -0,0 +1,18 @@ +/** + * Original comment. + */ +export interface Foo { + /** Overload 1 */ + (): void; + /** Overload 2 */ + (baz: number): void; +} + +// Inherits overload comments, but not Foo comment +// Foo comment could be inherited with {@inheritDoc Foo} +export const fooWithoutComment: Foo; + +/** + * New comment. + */ +export const fooWithComment: Foo; diff --git a/src/test/converter2/issues/gh2545.ts b/src/test/converter2/issues/gh2545.ts new file mode 100644 index 000000000..3061484fb --- /dev/null +++ b/src/test/converter2/issues/gh2545.ts @@ -0,0 +1,28 @@ +/** Parent docs */ +abstract class Parent { + /** notAbstract docs */ + notAbstract(): void {} + /** notAbstract2 docs */ + notAbstract2(): void {} + /** isAbstract docs */ + abstract isAbstract(): void; + /** abstractProperty docs */ + abstract abstractProperty: string; +} + +export class Child extends Parent { + override notAbstract2(): void {} + override isAbstract(): void {} + + override abstractProperty = ""; +} + +// #2084 +export class Foo { + /** @internal*/ + isInternal() {} +} + +export class Bar extends Foo { + isInternal() {} // also internal +} diff --git a/src/test/converter2/issues/gh2552.js b/src/test/converter2/issues/gh2552.js new file mode 100644 index 000000000..d4d97f3a0 --- /dev/null +++ b/src/test/converter2/issues/gh2552.js @@ -0,0 +1,17 @@ +/** + * Summary + * @license MIT + * + * Full permission notice. + */ + +// TS 5.5 @import comments +/** @import * as ts from "typescript" */ + +/** + * This is an awesome module. + * @module good-module + */ + +/** @import * as ts2 from "typescript" */ +export const something = 1; diff --git a/src/test/converter2/issues/gh2553.ts b/src/test/converter2/issues/gh2553.ts new file mode 100644 index 000000000..892454b11 --- /dev/null +++ b/src/test/converter2/issues/gh2553.ts @@ -0,0 +1,16 @@ +/** + * Constructor. + * + * @param args - Constructor arguments. + * @returns New instance. + */ +export type Constructor = new (...args: any[]) => object; + +/** + * Typed constructor. + * + * @typeParam T - Class type. + * @param args - Constructor arguments. + * @returns New instance. + */ +export type TypedConstructor<T> = new (...args: any[]) => T; diff --git a/src/test/converter2/issues/gh2574/default.ts b/src/test/converter2/issues/gh2574/default.ts new file mode 100644 index 000000000..196131c09 --- /dev/null +++ b/src/test/converter2/issues/gh2574/default.ts @@ -0,0 +1,6 @@ +/** + * Default export and class implementation + */ +export default class DefaultExport { + constructor() {} +} diff --git a/src/test/converter2/issues/gh2574/index.ts b/src/test/converter2/issues/gh2574/index.ts new file mode 100644 index 000000000..5f521cb67 --- /dev/null +++ b/src/test/converter2/issues/gh2574/index.ts @@ -0,0 +1,5 @@ +import { Default, NotDefault } from "./reexported"; + +export function usesDefaultExport(param: Default) {} + +export function usesNonDefaultExport(param: NotDefault) {} diff --git a/src/test/converter2/issues/gh2574/notDefault.ts b/src/test/converter2/issues/gh2574/notDefault.ts new file mode 100644 index 000000000..d2482e2c0 --- /dev/null +++ b/src/test/converter2/issues/gh2574/notDefault.ts @@ -0,0 +1,3 @@ +export class NotDefaultExport { + constructor() {} +} diff --git a/src/test/converter2/issues/gh2574/reexported.ts b/src/test/converter2/issues/gh2574/reexported.ts new file mode 100644 index 000000000..f09daf171 --- /dev/null +++ b/src/test/converter2/issues/gh2574/reexported.ts @@ -0,0 +1,2 @@ +export { default as Default } from "./default"; +export { NotDefaultExport as NotDefault } from "./notDefault"; diff --git a/src/test/converter2/issues/gh2582.ts b/src/test/converter2/issues/gh2582.ts new file mode 100644 index 000000000..7e65eddd9 --- /dev/null +++ b/src/test/converter2/issues/gh2582.ts @@ -0,0 +1,22 @@ +function getApi<T>(Ctor: new () => T) { + return { + /** Member comment */ + member: 1, + /** Fn comment */ + fn: () => new Ctor(), + }; +} + +function getAPIs<T1, T2>(Ctor1: new () => T1, Ctor2: new () => T2) { + const a = getApi(Ctor1); + + return { + /** A comment @namespace*/ + a, + /** B comment @namespace */ + b: getApi(Ctor2), + }; +} + +/** f32 comment @namespace */ +export const f32 = getAPIs(Float32Array, Float64Array); diff --git a/src/test/converter2/issues/gh2585.ts b/src/test/converter2/issues/gh2585.ts new file mode 100644 index 000000000..8bf1e280e --- /dev/null +++ b/src/test/converter2/issues/gh2585.ts @@ -0,0 +1,13 @@ +// prettier-ignore + +export type Foo = + /** + * Doc of foo1. + */ + | "foo1" + /** Not used */ + | + /** + * Doc of foo2. + */ + "foo2"; diff --git a/src/test/converter2/issues/gh2587.ts b/src/test/converter2/issues/gh2587.ts new file mode 100644 index 000000000..9a94e84dd --- /dev/null +++ b/src/test/converter2/issues/gh2587.ts @@ -0,0 +1,9 @@ +export function foo() { + const x = 1; + return { + /** + * Shorthand comment + */ + x, + }; +} diff --git a/src/test/converter2/issues/gh2603.ts b/src/test/converter2/issues/gh2603.ts new file mode 100644 index 000000000..c12406c6a --- /dev/null +++ b/src/test/converter2/issues/gh2603.ts @@ -0,0 +1,4 @@ +/** + * @author Ian Awesome + */ +export const x = 1; diff --git a/src/test/converter2/issues/gh2611.d.ts b/src/test/converter2/issues/gh2611.d.ts new file mode 100644 index 000000000..03ca47322 --- /dev/null +++ b/src/test/converter2/issues/gh2611.d.ts @@ -0,0 +1,4 @@ +/** + * @tagThatIsNotDefined + */ +export var num: number; diff --git a/src/test/converter2/issues/gh2614.ts b/src/test/converter2/issues/gh2614.ts new file mode 100644 index 000000000..2f495bb60 --- /dev/null +++ b/src/test/converter2/issues/gh2614.ts @@ -0,0 +1,4 @@ +/** + * @since 1.0.0 + */ +export function foo() {} diff --git a/src/test/events.test.ts b/src/test/events.test.ts index 4e74b18dd..5f3a1b107 100644 --- a/src/test/events.test.ts +++ b/src/test/events.test.ts @@ -1,957 +1,80 @@ -// Backbone.js 1.2.3 -// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Backbone may be freely distributed under the MIT license. -// For all details and documentation: -// https://backbonejs.org -// -// This test case was taken from -// https://github.com/jashkenas/backbone/blob/6b927eb5e7081af16f97d9c15e34b030624a68f9/test/events.js +import { deepStrictEqual as equal } from "assert"; +import { EventDispatcher } from "../lib/utils"; -import Assert = require("assert"); -import { EventDispatcher, Event } from "../lib/utils/events"; +describe("EventDispatcher", () => { + it("Works in simple cases", () => { + const emitter = new EventDispatcher<{ a: [] }>(); -function size(thing: any): number { - if (!thing) return 0; - return Object.keys(thing).length; -} - -class Events extends EventDispatcher { - counter = 0; - counterA = 0; - counterB = 0; -} - -describe("Events", function () { - it("on and trigger", function () { - const obj = new Events(); - obj.counter = 0; - - obj.on("event", function () { - obj.counter += 1; - }); - obj.trigger("event"); - Assert.equal(obj.counter, 1, "counter should be incremented."); - - obj.trigger("event"); - obj.trigger("event"); - obj.trigger("event"); - obj.trigger("event"); - Assert.equal( - obj.counter, - 5, - "counter should be incremented five times.", - ); - }); - - it("binding and triggering multiple events", function () { - const obj = new Events(); - obj.counter = 0; - - obj.on("a b c", function () { - obj.counter += 1; - }); - - obj.trigger("a"); - Assert.equal(obj.counter, 1); - - obj.trigger("a b"); - Assert.equal(obj.counter, 3); - - obj.trigger("c"); - Assert.equal(obj.counter, 4); - - obj.off("a c"); - obj.trigger("a b c"); - Assert.equal(obj.counter, 5); - }); - - it("binding and triggering with event maps", function () { - const obj = new Events(); - obj.counter = 0; - - const increment = function (this: Events) { - this.counter += 1; - }; - - obj.on( - { - a: increment, - b: increment, - c: increment, - }, - obj, - ); - - obj.trigger("a"); - Assert.equal(obj.counter, 1); - - obj.trigger("a b"); - Assert.equal(obj.counter, 3); - - obj.trigger("c"); - Assert.equal(obj.counter, 4); - - obj.off( - { - a: increment, - c: increment, - }, - obj, - ); - obj.trigger("a b c"); - Assert.equal(obj.counter, 5); - }); - - it("binding and triggering multiple event names with event maps", function () { - const obj = new Events(); - obj.counter = 0; - - const increment = function (this: Events) { - this.counter += 1; - }; - - obj.on({ - "a b c": increment, - }); - - obj.trigger("a"); - Assert.equal(obj.counter, 1); - - obj.trigger("a b"); - Assert.equal(obj.counter, 3); - - obj.trigger("c"); - Assert.equal(obj.counter, 4); - - obj.off({ - "a c": increment, - }); - obj.trigger("a b c"); - Assert.equal(obj.counter, 5); - }); - - it("binding and trigger with event maps context", function (this: any) { - const obj = new Events(); - obj.counter = 0; - const context = {}; - - obj.on( - { - a: function () { - Assert.strictEqual( - this, - context, - "defaults `context` to `callback` param", - ); - }, - }, - context, - ).trigger("a"); - - obj.off() - .on( - { - a: function (this: any) { - Assert.strictEqual( - this, - context, - "will not override explicit `context` param", - ); - }, - }, - this, - context, - ) - .trigger("a"); - }); - - it("listenTo and stopListening", function () { - const a = new Events(); - const b = new Events(); - - a.listenTo(b, "all", function () { - Assert(true); - }); - b.trigger("anything"); - - a.listenTo(b, "all", function () { - Assert(false); - }); - a.stopListening(); - b.trigger("anything"); - }); - - it("listenTo and stopListening with event maps", function () { - const a = new Events(); - const b = new Events(); - const cb = function () { - Assert(true); - }; - - a.listenTo(b, { event: cb }); - b.trigger("event"); - - a.listenTo(b, { event2: cb }); - b.on("event2", cb); - a.stopListening(b, { event2: cb }); - b.trigger("event event2"); - - a.stopListening(); - b.trigger("event event2"); - }); - - it("stopListening with omitted args", function () { - const a = new Events(); - const b = new Events(); - const cb = function () { - Assert(true); - }; - a.listenTo(b, "event", cb); - b.on("event", cb); - a.listenTo(b, "event2", cb); - a.stopListening(undefined, { event: cb }); - b.trigger("event event2"); - b.off(); - a.listenTo(b, "event event2", cb); - a.stopListening(undefined, "event"); - a.stopListening(); - b.trigger("event2"); - }); - - it("listenToOnce", function () { - // Same as the previous test, but we use once rather than having to explicitly unbind - const obj = new Events(); - obj.counterA = 0; - obj.counterB = 0; - - const incrA = function () { - obj.counterA += 1; - obj.trigger("event"); - }; - const incrB = function () { - obj.counterB += 1; - }; - obj.listenToOnce(obj, "event", incrA); - obj.listenToOnce(obj, "event", incrB); - obj.trigger("event"); - Assert.equal( - obj.counterA, - 1, - "counterA should have only been incremented once.", - ); - Assert.equal( - obj.counterB, - 1, - "counterB should have only been incremented once.", - ); - }); - - it("listenToOnce and stopListening", function () { - const a = new Events(); - const b = new Events(); - a.listenToOnce(b, "all", function () { - Assert(true); - }); - b.trigger("anything"); - b.trigger("anything"); - a.listenToOnce(b, "all", function () { - Assert(false); - }); - a.stopListening(); - b.trigger("anything"); - }); - - it("listenTo, listenToOnce and stopListening", function () { - const a = new Events(); - const b = new Events(); - a.listenToOnce(b, "all", function () { - Assert(true); - }); - b.trigger("anything"); - b.trigger("anything"); - a.listenTo(b, "all", function () { - Assert(false); - }); - a.stopListening(); - b.trigger("anything"); - }); - - it("listenTo and stopListening with event maps", function () { - const a = new Events(); - const b = new Events(); - a.listenTo(b, { - change: function () { - Assert(true); - }, - }); - b.trigger("change"); - a.listenTo(b, { - change: function () { - Assert(false); - }, - }); - a.stopListening(); - b.trigger("change"); - }); - - it("listenTo yourself", function () { - const e = new Events(); - e.listenTo(e, "foo", function () { - Assert(true); - }); - e.trigger("foo"); - }); - - it("listenTo yourself cleans yourself up with stopListening", function () { - const e = new Events(); - e.listenTo(e, "foo", function () { - Assert(true); - }); - e.trigger("foo"); - - e.listenTo(e, "foo", function () { - Assert(false); - }); - e.stopListening(); - e.trigger("foo"); - }); - - it("stopListening cleans up references", function () { - const a: any = new Events(); - const b: any = new Events(); - const fn = function () { - // nop - }; - b.on("event", fn); - - a.listenTo(b, "event", fn).stopListening(); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn).stopListening(b); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn).stopListening(b, "event"); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn).stopListening(b, "event", fn); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - }); - - it("stopListening cleans up references from listenToOnce", function () { - const a: any = new Events(); - const b: any = new Events(); - const fn = function () { - // nop - }; - b.on("event", fn); - - a.listenToOnce(b, "event", fn).stopListening(); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenToOnce(b, "event", fn).stopListening(b); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenToOnce(b, "event", fn).stopListening(b, "event"); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - - a.listenToOnce(b, "event", fn).stopListening(b, "event", fn); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._events.event), 1); - Assert.equal(size(b._listeners), 0); - }); - - it("listenTo and off cleaning up references", function () { - const a: any = new Events(); - const b: any = new Events(); - const fn = function () { - // nop - }; - - a.listenTo(b, "event", fn); - b.off(); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn); - b.off("event"); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn); - b.off(null, fn); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._listeners), 0); - - a.listenTo(b, "event", fn); - b.off(null, null, a); - Assert.equal(size(a._listeningTo), 0); - Assert.equal(size(b._listeners), 0); - }); - - it("listenTo and stopListening cleaning up references", function () { - const a = new Events(); - const b = new Events(); - - a.listenTo(b, "all", function () { - Assert(true); - }); - b.trigger("anything"); - - a.listenTo(b, "other", function () { - Assert(false); - }); - a.stopListening(b, "other"); - a.stopListening(b, "all"); - - Assert.equal(size((<any>a)._listeningTo), 0); - }); - - it("listenToOnce without context cleans up references after the event has fired", function () { - const a = new Events(); - const b = new Events(); - a.listenToOnce(b, "all", function () { - Assert(true); + let calls = 0; + emitter.on("a", () => { + calls++; }); - b.trigger("anything"); - Assert.equal(size((<any>a)._listeningTo), 0); - }); - - it("listenToOnce with event maps cleans up references", function () { - const a = new Events(); - const b = new Events(); - a.listenToOnce(b, { - one: function () { - Assert(true); - }, - two: function () { - Assert(false); - }, - }); - b.trigger("one"); - Assert.equal(size((<any>a)._listeningTo), 1); - }); - - it("listenToOnce with event maps binds the correct `this`", function () { - const a = new Events(); - const b = new Events(); - a.listenToOnce(b, { - one: function (this: Events) { - Assert(this === a); - }, - two: function () { - Assert(false); - }, - }); - b.trigger("one"); - }); - - it("listenTo with empty callback does not throw an error", function () { - const e = new Events(); - e.listenTo(e, "foo", undefined); - e.trigger("foo"); - Assert(true); - }); - - it("trigger all for each event", function () { - let a = false; - let b = false; - const obj = new Events(); - obj.counter = 0; - obj.on("all", function (event: string) { - obj.counter++; - if (event === "a") { - a = true; - } - if (event === "b") { - b = true; - } - }).trigger("a b"); - Assert(a); - Assert(b); - Assert.equal(obj.counter, 2); - }); - - it("on, then unbind all functions", function () { - const obj = new Events(); - obj.counter = 0; - const callback = function () { - obj.counter += 1; - }; + equal(calls, 0); - obj.on("event", callback); - obj.trigger("event"); - obj.off("event"); - obj.trigger("event"); - Assert.equal( - obj.counter, - 1, - "counter should have only been incremented once.", - ); + emitter.trigger("a"); + equal(calls, 1); + emitter.trigger("a"); + equal(calls, 2); }); - it("bind two callbacks, unbind only one", function () { - const obj = new Events(); - obj.counterA = 0; - obj.counterB = 0; - const callback = function () { - obj.counterA += 1; - }; - obj.on("event", callback); - obj.on("event", function () { - obj.counterB += 1; - }); - obj.trigger("event"); - obj.off("event", callback); - obj.trigger("event"); - Assert.equal( - obj.counterA, - 1, - "counterA should have only been incremented once.", - ); - Assert.equal( - obj.counterB, - 2, - "counterB should have been incremented twice.", - ); - }); + it("Allows removing listeners", () => { + const emitter = new EventDispatcher<{ a: [] }>(); - it("unbind a callback in the midst of it firing", function () { - const obj = new Events(); - obj.counter = 0; - const callback = function () { - obj.counter += 1; - obj.off("event", callback); + let calls = 0; + const listener = () => { + calls++; }; - obj.on("event", callback); - obj.trigger("event"); - obj.trigger("event"); - obj.trigger("event"); - Assert.equal(obj.counter, 1, "the callback should have been unbound."); - }); - - it("two binds that unbind themeselves", function () { - const obj = new Events(); - obj.counterA = 0; - obj.counterB = 0; - const incrA = function () { - obj.counterA += 1; - obj.off("event", incrA); - }; - const incrB = function () { - obj.counterB += 1; - obj.off("event", incrB); - }; - obj.on("event", incrA); - obj.on("event", incrB); - obj.trigger("event"); - obj.trigger("event"); - obj.trigger("event"); - Assert.equal( - obj.counterA, - 1, - "counterA should have only been incremented once.", - ); - Assert.equal( - obj.counterB, - 1, - "counterB should have only been incremented once.", - ); - }); - - it("bind a callback with a supplied context", function () { - class TestClass { - assertTrue() { - Assert(true, "`this` was bound to the callback"); - } - } - - const obj = new Events(); - obj.on( - "event", - function (this: TestClass) { - this.assertTrue(); - }, - new TestClass(), - ); - obj.trigger("event"); - }); + emitter.off("a", listener); + equal(calls, 0); - it("nested trigger with unbind", function () { - const obj = new Events(); - obj.counter = 0; - const incr1 = function () { - obj.counter += 1; - obj.off("event", incr1); - obj.trigger("event"); - }; - const incr2 = function () { - obj.counter += 1; - }; - obj.on("event", incr1); - obj.on("event", incr2); - obj.trigger("event"); - Assert.equal( - obj.counter, - 3, - "counter should have been incremented three times", - ); + emitter.trigger("a"); + equal(calls, 0); }); - it("callback list is not altered during trigger", function () { - let counter = 0; - const obj = new Events(); - const incr = function () { - counter++; - }; - const incrOn = function () { - obj.on("event all", incr); - }; - const incrOff = function () { - obj.off("event all", incr); - }; - - obj.on("event all", incrOn).trigger("event"); - Assert.equal(counter, 0, "on does not alter callback list"); + it("Works correctly with missing listeners", () => { + const emitter = new EventDispatcher<{ a: [] }>(); - obj.off().on("event", incrOff).on("event all", incr).trigger("event"); - Assert.equal(counter, 2, "off does not alter callback list"); - }); - - it("#1282 - 'all' callback list is retrieved after each event.", function () { - let counter = 0; - const obj = new Events(); - const incr = function () { - counter++; + let calls = 0; + const listener = () => { + calls++; }; - - obj.on("x", function () { - obj.on("y", incr).on("all", incr); - }).trigger("x y"); - - Assert.strictEqual(counter, 2); - }); - - it("if no callback is provided, `on` is a noop", function () { - (<any>new Events()).on("test").trigger("test"); - }); - - it("if callback is truthy but not a function, `on` should throw an error just like jQuery", function () { - const view = (<any>new Events()).on("test", "noop"); - Assert.throws(function () { - view.trigger("test"); + emitter.on("a", () => { + calls++; }); - }); - - it("remove all events for a specific context", function () { - const obj = new Events(); - obj.on("x y all", function () { - Assert(true); - }); - obj.on( - "x y all", - function () { - Assert(false); - }, - obj, - ); - obj.off(undefined, undefined, obj); - obj.trigger("x y"); - }); - - it("remove all events for a specific callback", function () { - const obj = new Events(); - const success = function () { - Assert(true); - }; - const fail = function () { - Assert(false); - }; - obj.on("x y all", success); - obj.on("x y all", fail); - obj.off(undefined, fail); - obj.trigger("x y"); - }); - - it("#1310 - off does not skip consecutive events", function () { - const obj = new Events(); - obj.on( - "event", - function () { - Assert(false); - }, - obj, - ); - obj.on( - "event", - function () { - Assert(false); - }, - obj, - ); - obj.off(undefined, undefined, obj); - obj.trigger("event"); - }); - - it("once", function () { - // Same as the previous test, but we use once rather than having to explicitly unbind - const obj = new Events(); - obj.counterA = 0; - obj.counterB = 0; + emitter.off("a", listener); - const incrA = function () { - obj.counterA += 1; - obj.trigger("event"); - }; - const incrB = function () { - obj.counterB += 1; - }; - obj.once("event", incrA); - obj.once("event", incrB); - obj.trigger("event"); - Assert.equal( - obj.counterA, - 1, - "counterA should have only been incremented once.", - ); - Assert.equal( - obj.counterB, - 1, - "counterB should have only been incremented once.", - ); - }); - - it("once variant one", function () { - let count = 0; - const f = function () { - count += 1; - }; - - const a = new Events().once("event", f); - const b = new Events().on("event", f); - - a.trigger("event"); - b.trigger("event"); - b.trigger("event"); - - Assert.equal(count, 3); - }); - - it("once variant two", function () { - let count = 0; - const obj = new Events(); - const f = function () { - count += 1; - }; - - obj.once("event", f).on("event", f).trigger("event").trigger("event"); - - Assert.equal(count, 3); - }); - - it("once with off", function () { - const obj = new Events(); - const f = function () { - Assert(false); - }; - - obj.once("event", f); - obj.off("event", f); - obj.trigger("event"); - }); - - it("once with event maps", function () { - const obj = new Events(); - obj.counter = 0; - - const increment = function (this: Events) { - this.counter += 1; - }; - - obj.once( - { - a: increment, - b: increment, - c: increment, - }, - obj, - ); - - obj.trigger("a"); - Assert.equal(obj.counter, 1); - - obj.trigger("a b"); - Assert.equal(obj.counter, 2); - - obj.trigger("c"); - Assert.equal(obj.counter, 3); - - obj.trigger("a b c"); - Assert.equal(obj.counter, 3); + emitter.trigger("a"); + equal(calls, 1); }); - it("once with off only by context", function () { - const context = {}; - const obj = new Events(); - obj.once( - "event", - function () { - Assert(false); - }, - context, - ); - obj.off(undefined, undefined, context); - obj.trigger("event"); - }); + it("Works if a listener is removed while emitting", () => { + const emitter = new EventDispatcher<{ a: [] }>(); - it("once with multiple events.", function () { - let count = 0; - const obj = new Events(); - obj.once("x y", function () { - count += 1; + let calls = 0; + emitter.on("a", function rem() { + calls++; + emitter.off("a", rem); }); - obj.trigger("x y"); - Assert.equal(count, 2); - }); - - it("Off during iteration with once.", function () { - let count = 0; - const obj = new Events(); - const f = function (this: Events) { - this.off("event", f); - }; - obj.on("event", f); - obj.once("event", function () { - // nop + emitter.on("a", () => { + calls++; }); - obj.on("event", function () { - count += 1; - }); - - obj.trigger("event"); - obj.trigger("event"); - Assert.equal(count, 2); - }); - - it("`once` on `all` should work as expected", function () { - let count = 0; - const obj = new Events(); - obj.once("all", function () { - count += 1; - obj.trigger("all"); - }); - obj.trigger("all"); - - Assert.equal(count, 1); - }); - - it("once without a callback is a noop", function () { - (<any>new Events()).once("event").trigger("event"); - }); + equal(calls, 0); - it("listenToOnce without a callback is a noop", function () { - const obj = new Events(); - (<any>obj).listenToOnce(obj, "event").trigger("event"); + emitter.trigger("a"); + equal(calls, 2); + emitter.trigger("a"); + equal(calls, 3); }); - it("event functions are chainable", function () { - const obj = new Events(); - const obj2 = new Events(); - const fn = function () { - // nop - }; - - Assert.equal(obj, obj.trigger("noeventssetyet")); - Assert.equal(obj, obj.off("noeventssetyet")); - Assert.equal(obj, obj.stopListening(undefined, "noeventssetyet")); - Assert.equal(obj, obj.on("a", fn)); - Assert.equal(obj, obj.once("c", fn)); - Assert.equal(obj, obj.trigger("a")); - Assert.equal(obj, obj.listenTo(obj2, "a", fn)); - Assert.equal(obj, obj.listenToOnce(obj2, "b", fn)); - Assert.equal(obj, obj.off("a c")); - Assert.equal(obj, obj.stopListening(obj2, "a")); - Assert.equal(obj, obj.stopListening()); - }); - - it("#3448 - listenToOnce with space-separated events", function () { - const one = new Events(); - const two = new Events(); - let count = 1; - one.listenToOnce(two, "x y", function (n: number) { - Assert(n === count++); - }); - two.trigger("x", 1); - two.trigger("x", 1); - two.trigger("y", 2); - two.trigger("y", 2); - }); -}); + it("Calls listeners according to their order", () => { + const emitter = new EventDispatcher<{ a: [] }>(); -describe("Events (customized)", function () { - it("accepts event objects", function () { - let count = 0; - const events = new Events(); - const event = new Event("myEvent"); - events.on("myEvent", function (e: Event) { - Assert(e instanceof Event); - count += 1; - }); - events.trigger(event); - Assert.equal(count, 1); - }); - - it("stops propagation", function () { - let count = 0; - const events = new Events(); - const event = new Event("myEvent"); - events.on("myEvent", function (e: Event) { - count++; - e.stopPropagation(); - }); - events.on("myEvent", function () { - count++; - Assert(false); - }); - events.trigger(event); - Assert.equal(count, 1); - }); + const calls: number[] = []; + emitter.on("a", () => calls.push(3), 25); + emitter.on("a", () => calls.push(2), 50); + emitter.on("a", () => calls.push(1), 50); - it("sorts handlers by priority", function () { - let count = 0; - const events = new Events(); - events.on( - "myEvent", - function () { - Assert.equal(count, 1); - count++; - }, - void 0, - 0, - ); - events.on( - "myEvent", - function () { - Assert.equal(count, 0); - count++; - }, - void 0, - 100, - ); - events.trigger("myEvent"); - Assert.equal(count, 2); + emitter.trigger("a"); + equal(calls, [1, 2, 3]); }); }); diff --git a/src/test/internationalization.test.ts b/src/test/internationalization.test.ts new file mode 100644 index 000000000..293ce8c78 --- /dev/null +++ b/src/test/internationalization.test.ts @@ -0,0 +1,96 @@ +import { deepEqual as equal, ok } from "assert/strict"; +import { Application } from ".."; +import { readdirSync } from "fs"; +import { join } from "path"; +import { translatable } from "../lib/internationalization/translatable"; +import { setDifference } from "../lib/utils/set"; +import { + blockTags, + inlineTags, + modifierTags, +} from "../lib/utils/options/tsdoc-defaults"; + +const allValidTranslationKeys = Object.keys(translatable); +// The tag names do not actually exist in the default locale, but are valid +// for translation, so include them here. +allValidTranslationKeys.push(...blockTags.map((s) => "tag_" + s.substring(1))); +allValidTranslationKeys.push( + ...modifierTags.map((s) => "tag_" + s.substring(1)), +); +allValidTranslationKeys.push(...inlineTags.map((s) => "tag_" + s.substring(1))); + +describe("Internationalization", () => { + let app: Application; + before(async () => { + app = await Application.bootstrap({}, []); + }); + + afterEach(() => { + app.options.reset(); + }); + + it("Supports getting the list of supported languages", () => { + const langs = app.internationalization.getSupportedLanguages(); + ok(langs.includes("en")); + ok(langs.includes("ko")); + ok(langs.includes("jp")); + }); + + it("Supports translating without placeholders", () => { + equal( + app.i18n.no_entry_points_to_merge(), + "No entry points provided to merge", + ); + app.options.setValue("lang", "zh"); + equal(app.i18n.no_entry_points_to_merge(), "没有提供合并的入口点"); + }); + + it("Supports translating with placeholders", () => { + equal( + app.i18n.docs_generated_at_0("X"), + "Documentation generated at X", + ); + app.options.setValue("lang", "zh"); + equal(app.i18n.docs_generated_at_0("X"), "文档生成于 X"); + }); +}); + +describe("Locales", () => { + const localeRoot = join(__dirname, "../lib/internationalization/locales"); + + for (const locale of readdirSync(localeRoot)) { + it(`${locale} defines a valid locale`, () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const translations = require(join(localeRoot, locale)) as Record< + string, + string + >; + + for (const [key, translation] of Object.entries(translations)) { + const validPlaceholders = Array.from( + key.matchAll(/_(\d+)_|(\d+)$/g), + (m) => m[1] || m[2], + ); + + for (const placeholder of translation.matchAll(/\{(\d+?)\}/g)) { + ok( + validPlaceholders.includes(placeholder[1]), + `${key} translation references "${placeholder[0]}" which will not be available at runtime.`, + ); + } + } + + const extraKeys = Array.from( + setDifference( + Object.keys(translations), + allValidTranslationKeys, + ), + ); + equal( + extraKeys, + [], + `${locale} defines translations which do not exist in the default locale.`, + ); + }); + } +}); diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index 7c2c69425..bb3866389 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -12,7 +12,7 @@ import { DeclarationReflection, IntrinsicType, LiteralType, - ProjectReflection, + type ProjectReflection, QueryType, ReferenceReflection, ReflectionKind, @@ -27,7 +27,14 @@ import { getConverter2Program, } from "./programs"; import { TestLogger } from "./TestLogger"; -import { getComment, getLinks, query, querySig } from "./utils"; +import { + equalKind, + getComment, + getLinks, + getSigComment, + query, + querySig, +} from "./utils"; import { DefaultTheme, PageEvent } from ".."; const base = getConverter2Base(); @@ -112,7 +119,10 @@ describe("Issue Tests", () => { (r) => r.name === "Foo" && r.kind === ReflectionKind.Class, ); ok(classFoo instanceof DeclarationReflection); - equal(classFoo.children?.find((r) => r.name === "x"), undefined); + equal( + classFoo.children?.find((r) => r.name === "x"), + undefined, + ); const nsFoo = project.children?.find( (r) => r.name === "Foo" && r.kind === ReflectionKind.Namespace, @@ -121,7 +131,7 @@ describe("Issue Tests", () => { ok(nsFoo.children?.find((r) => r.name === "x")); }); - it("Supports computed names #941", () => { + it("#941 Supports computed names ", () => { const project = convert(); const obj = query(project, "Obj"); equal( @@ -146,7 +156,7 @@ describe("Issue Tests", () => { it("#1150", () => { const project = convert(); const refl = query(project, "IntersectFirst"); - equal(refl?.kind, ReflectionKind.TypeAlias); + equal(refl.kind, ReflectionKind.TypeAlias); equal(refl.type?.type, "indexedAccess"); }); @@ -191,7 +201,7 @@ describe("Issue Tests", () => { it("#1330", () => { const project = convert(); const example = query(project, "ExampleParam"); - equal(example?.type?.type, "reference"); + equal(example.type?.type, "reference"); equal(example.type.toString(), "Example"); }); @@ -203,15 +213,17 @@ describe("Issue Tests", () => { it("#1408", () => { const project = convert(); - const foo = query(project, "foo"); - const type = foo?.signatures?.[0]?.typeParameters?.[0].type; - equal(type?.type, "array"); + const foo = querySig(project, "foo"); + const type = foo.typeParameters?.[0].type; equal(type?.toString(), "unknown[]"); }); it("#1436", () => { const project = convert(); - equal(project.children?.map((c) => c.name), ["gh1436"]); + equal( + project.children?.map((c) => c.name), + ["gh1436"], + ); }); it("#1449", () => { @@ -225,15 +237,13 @@ describe("Issue Tests", () => { it("#1454", () => { const project = convert(); - const foo = query(project, "foo"); - const fooRet = foo?.signatures?.[0]?.type; - equal(fooRet?.type, "reference"); - equal(fooRet?.toString(), "Foo"); + const foo = querySig(project, "foo"); + equal(foo.type?.type, "reference"); + equal(foo.type.toString(), "Foo"); - const bar = query(project, "bar"); - const barRet = bar?.signatures?.[0]?.type; - equal(barRet?.type, "reference"); - equal(barRet?.toString(), "Bar"); + const bar = querySig(project, "bar"); + equal(bar.type?.type, "reference"); + equal(bar.type.toString(), "Bar"); }); it("#1462", () => { @@ -303,9 +313,10 @@ describe("Issue Tests", () => { it("#1522", () => { app.options.setValue("categorizeByGroup", true); const project = convert(); - equal(project.groups?.map((g) => g.categories?.map((c) => c.title)), [ - ["cat"], - ]); + equal( + project.groups?.map((g) => g.categories?.map((c) => c.title)), + [["cat"]], + ); }); it("#1524", () => { @@ -330,11 +341,10 @@ describe("Issue Tests", () => { it("#1547", () => { const project = convert(); - equal(project.children?.map((c) => c.name), [ - "Test", - "ThingA", - "ThingB", - ]); + equal( + project.children?.map((c) => c.name), + ["Test", "ThingA", "ThingB"], + ); }); it("#1552", () => { @@ -384,26 +394,29 @@ describe("Issue Tests", () => { const project = convert(); const ctor = query(project, "Foo.constructor"); equal(ctor.sources?.[0]?.line, 2); - equal(ctor.sources?.[0]?.character, 4); + equal(ctor.sources[0].character, 4); }); - it("Handles comment discovery with expando functions #1651", () => { + it("#1651 Handles comment discovery with expando functions ", () => { const project = convert(); - equal(project.children?.map((c) => c.name), ["bar"]); + equal( + project.children?.map((c) => c.name), + ["bar"], + ); - equal(project.children[0].children?.map((c) => c.name), [ - "metadata", - "fn", - ]); + equal( + project.children[0].children?.map((c) => c.name), + ["metadata", "fn"], + ); const comments = [ - project.children[0].comment?.summary, - project.children[0].children[0].comment?.summary, - project.children[0].children[1].signatures![0].comment?.summary, - project.children[0].signatures![0].comment?.summary, - ].map(Comment.combineDisplayParts); + query(project, "bar"), + query(project, "bar.metadata"), + querySig(project, "bar.fn"), + querySig(project, "bar"), + ].map((r) => Comment.combineDisplayParts(r.comment?.summary)); - equal(comments, ["", "metadata", "fn", "bar"]); + equal(comments, ["bar", "metadata", "fn", ""]); }); it("#1660", () => { @@ -436,6 +449,8 @@ describe("Issue Tests", () => { ]), ]; equal(alias.comment, expectedComment); + + logger.expectMessage("warn: Encountered an unknown block tag @asdf"); }); it("#1745", () => { @@ -455,8 +470,8 @@ describe("Issue Tests", () => { ok(!Foo.comment?.getTag("@category"), "has cat tag"); ok(!Foo.type.declaration.comment?.getTag("@category"), "has cat tag 2"); ok( - !Foo.type.declaration.signatures?.some( - (s) => s.comment?.getTag("@category"), + !Foo.type.declaration.signatures?.some((s) => + s.comment?.getTag("@category"), ), "has cat tag 3", ); @@ -466,7 +481,7 @@ describe("Issue Tests", () => { const project = convert(); const sym1 = query(project, "sym1"); equal( - Comment.combineDisplayParts(sym1.signatures?.[0].comment?.summary), + Comment.combineDisplayParts(sym1.comment?.summary), "Docs for Sym1", ); @@ -493,9 +508,12 @@ describe("Issue Tests", () => { it("#1795", () => { const project = convert(); - equal(project.children?.map((c) => c.name), ["default", "foo"]); - ok(project.children![0].kind === ReflectionKind.Reference); - ok(project.children![1].kind !== ReflectionKind.Reference); + equal( + project.children?.map((c) => c.name), + ["default", "foo"], + ); + ok(project.children[0].kind === ReflectionKind.Reference); + ok(project.children[1].kind !== ReflectionKind.Reference); }); it("#1804", () => { @@ -511,10 +529,10 @@ describe("Issue Tests", () => { it("#1875", () => { const project = convert(); const test = query(project, "test"); - equal(test.signatures?.[0].parameters?.map((p) => p.type?.toString()), [ - "typeof globalThis", - "string", - ]); + equal( + test.signatures?.[0].parameters?.map((p) => p.type?.toString()), + ["typeof globalThis", "string"], + ); const test2 = query(project, "test2"); equal( @@ -588,7 +606,7 @@ describe("Issue Tests", () => { const project = convert(); app.validate(project); logger.expectMessage( - "warn: UnDocFn (TypeAlias), defined in */gh1898.ts, does not have any documentation.", + "warn: UnDocFn (TypeAlias), defined in */gh1898.ts, does not have any documentation", ); }); @@ -705,9 +723,7 @@ describe("Issue Tests", () => { equal(comments, ["A override", "B module"]); const comments2 = ["A.a", "B.b"].map((n) => - Comment.combineDisplayParts( - query(project, n).signatures![0].comment?.summary, - ), + Comment.combineDisplayParts(query(project, n).comment?.summary), ); equal(comments2, ["Comment for a", "Comment for b"]); @@ -716,26 +732,29 @@ describe("Issue Tests", () => { it("#1980", () => { const project = convert(); const link = query(project, "link"); - equal(link.comment?.summary.filter((t) => t.kind === "inline-tag"), [ - { - kind: "inline-tag", - tag: "@link", - target: "http://example.com", - text: "http://example.com", - }, - { - kind: "inline-tag", - tag: "@link", - target: "http://example.com", - text: "with text", - }, - { - kind: "inline-tag", - tag: "@link", - target: "http://example.com", - text: "jsdoc support", - }, - ]); + equal( + link.comment?.summary.filter((t) => t.kind === "inline-tag"), + [ + { + kind: "inline-tag", + tag: "@link", + target: "http://example.com", + text: "http://example.com", + }, + { + kind: "inline-tag", + tag: "@link", + target: "http://example.com", + text: "with text", + }, + { + kind: "inline-tag", + tag: "@link", + target: "http://example.com", + text: "jsdoc support", + }, + ], + ); }); it("#1986", () => { @@ -765,19 +784,19 @@ describe("Issue Tests", () => { it("#1996", () => { const project = convert(); - const a = query(project, "a"); - equal(a.signatures![0].sources?.[0].fileName, "gh1996.ts"); - equal(a.signatures![0].sources?.[0].line, 1); - equal(a.signatures![0].sources?.[0].character, 17); - const b = query(project, "b"); - equal(b.signatures![0].sources?.[0].fileName, "gh1996.ts"); - equal(b.signatures![0].sources?.[0].line, 3); - equal(b.signatures![0].sources?.[0].character, 16); + const a = querySig(project, "a"); + equal(a.sources?.[0].fileName, "gh1996.ts"); + equal(a.sources[0].line, 1); + equal(a.sources[0].character, 17); + const b = querySig(project, "b"); + equal(b.sources?.[0].fileName, "gh1996.ts"); + equal(b.sources[0].line, 3); + equal(b.sources[0].character, 16); }); it("#2008", () => { const project = convert(); - const fn = query(project, "myFn").signatures![0]; + const fn = query(project, "myFn"); equal(Comment.combineDisplayParts(fn.comment?.summary), "Docs"); }); @@ -876,20 +895,25 @@ describe("Issue Tests", () => { it("#2042", () => { const project = convert(); - for (const [name, docs] of [ - ["built", "inner docs"], - ["built2", "outer docs"], - ["fn", "inner docs"], - ["fn2", "outer docs"], + for (const [name, docs, sigDocs] of [ + ["built", "", "inner docs"], + ["built2", "outer docs", "inner docs"], + ["fn", "", "inner docs"], + ["fn2", "outer docs", "inner docs"], ]) { const refl = query(project, name); ok(refl.signatures?.[0]); + equal( + Comment.combineDisplayParts(refl.comment?.summary), + docs, + name + " docs", + ); equal( Comment.combineDisplayParts( refl.signatures[0].comment?.summary, ), - docs, - name, + sigDocs, + name + " sig docs", ); } }); @@ -909,6 +933,7 @@ describe("Issue Tests", () => { }); it("#2064", () => { + app.options.setValue("excludePrivate", false); const project = convert(); query(project, "PrivateCtorDecl.x"); }); @@ -929,7 +954,7 @@ describe("Issue Tests", () => { ); }); - it("Handles types/values with same name #2106", () => { + it("#2106 Handles types/values with same name ", () => { const project = convert(); const balance = querySig(project, "balance"); equal(balance.type?.type, "reference"); @@ -994,7 +1019,7 @@ describe("Issue Tests", () => { const foo = query(project, "foo"); equal(foo.signatures?.length, 1); equal( - Comment.combineDisplayParts(foo.signatures[0].comment?.summary), + Comment.combineDisplayParts(foo.comment?.summary), "Is documented", ); }); @@ -1073,20 +1098,19 @@ describe("Issue Tests", () => { equal( clsSig!.implementationOf?.reflection?.getFullName(), - intTarget!.getFullName(), + intTarget.getFullName(), `${name} signature not properly linked`, ); } }); - it("Handles implementationOf with symbols #2234", () => { + it("#2234 Handles implementationOf with symbols ", () => { const project = convert(); const cm = query(project, "CharMap"); - equal(cm.children?.map((c) => c.name), [ - "constructor", - "[iterator]", - "at", - ]); + equal( + cm.children?.map((c) => c.name), + ["constructor", "[iterator]", "at"], + ); equal( cm.children[1].implementationOf?.name, @@ -1094,7 +1118,7 @@ describe("Issue Tests", () => { ); }); - it("Handles http links with TS link resolution #2270", () => { + it("#2270 Handles http links with TS link resolution ", () => { const project = convert(); const links = getLinks(query(project, "A")); equal(links, [ @@ -1109,7 +1133,7 @@ describe("Issue Tests", () => { ]); }); - it("Handles comments on interfaces with call signatures #2290", () => { + it("#2290 Handles comments on interfaces with call signatures ", () => { const project = convert(); equal(getComment(project, "CallSignature"), "Int comment"); @@ -1132,14 +1156,14 @@ describe("Issue Tests", () => { ); }); - it("Does not warn on notDocumented edge case #2291", () => { + it("#2291 Does not warn on notDocumented edge case ", () => { app.options.setValue("validation", { notDocumented: true }); const project = convert(); app.validate(project); logger.expectNoOtherMessages(); }); - it("Supports TS 5.0 #2296", () => { + it("#2296 Supports TS 5.0 ", () => { const project = convert(); const names = query(project, "names"); equal(names.type?.toString(), 'readonly ["Alice", "Bob", "Eve"]'); @@ -1150,7 +1174,7 @@ describe("Issue Tests", () => { equal(tp.flags.isConst, true); }); - it("Detects source locations coming from types and prefers value declarations, #2307", () => { + it("#2307 Detects source locations coming from types and prefers value declarations, ", () => { const project = convert(); const getLines = (name: string) => { @@ -1165,14 +1189,14 @@ describe("Issue Tests", () => { equal(getLines("all"), [8, 9]); }); - it("Uses type parameters from parent class in arrow-methods, #2320", () => { + it("#2320 Uses type parameters from parent class in arrow-methods, ", () => { const project = convert(); const arrow = querySig(project, "ResolvedSubclass.arrowFunction"); equal(arrow.typeParameters![0].type?.toString(), '"one" | "two"'); }); - it("Handles comments with nested methods #2336", () => { + it("#2336 Handles comments with nested methods ", () => { const project = convert(); const outer = querySig(project, "ClassVersion.outer"); @@ -1187,7 +1211,7 @@ describe("Issue Tests", () => { ); }); - it("Supports nested paths with tsLinkResolution #2360", () => { + it("#2360 Supports nested paths with tsLinkResolution ", () => { const project = convert(); const x = query(project, "x"); const link = x.comment?.summary[0]; @@ -1195,22 +1219,31 @@ describe("Issue Tests", () => { equal(link.target, query(project, "Foo.bar")); }); - it("Handles duplicate declarations with @namespace #2364", () => { + it("#2364 Handles duplicate declarations with @namespace ", () => { const project = convert(); - equal(project.children?.map((c) => c.name), ["NS", "NS2", "NS2"]); + equal( + project.children?.map((c) => c.name), + ["NS", "NS2", "NS2"], + ); const ns = query(project, "NS"); - equal(ns.children?.map((c) => c.name), ["T", "property"]); + equal( + ns.children?.map((c) => c.name), + ["T", "property"], + ); }); - it("Gets properties when types/variables are merged with @namespace #2364", () => { + it("#2364 Gets properties when types/variables are merged with @namespace ", () => { const project = convert(); const ns = project.children?.find( (c) => c.name == "NS2" && c.kind == ReflectionKind.Namespace, ); - equal(ns?.children?.map((c) => c.name), ["property"]); + equal( + ns?.children?.map((c) => c.name), + ["property"], + ); }); - it("Puts delegate type alias comments on the type alias #2372", () => { + it("#2372 Puts delegate type alias comments on the type alias ", () => { const project = convert(); equal( getComment(project, "EventHandler"), @@ -1226,7 +1259,7 @@ describe("Issue Tests", () => { equal(Comment.combineDisplayParts(typeSig?.comment?.summary), ""); }); - it("Handles spaces in JSDoc default parameter names #2384", () => { + it("#2384 Handles spaces in JSDoc default parameter names ", () => { const project = convert(); const Typed = query(project, "Typed"); equal(Typed.typeParameters?.length, 1); @@ -1238,7 +1271,7 @@ describe("Issue Tests", () => { ); }); - it("Handles @template parameter constraints correctly, #2389", () => { + it("#2389 Handles @template parameter constraints correctly, ", () => { const project = convert(); const foo = query(project, "foo"); equal(foo.signatures?.length, 1); @@ -1253,7 +1286,7 @@ describe("Issue Tests", () => { // a single declare module can still have a comment on them, but it looks really // weird and wrong if there are multiple declare module statements in a file... // there's probably some nicer way of doing this that I'm not seeing right now. - it("Uses module comment discovery on 'declare module \"foo\"' #2401", () => { + it("#2401 Uses module comment discovery on 'declare module \"foo\"' ", () => { const project = convert(); equal( Comment.combineDisplayParts(project.comment?.summary), @@ -1261,37 +1294,35 @@ describe("Issue Tests", () => { ); }); - it("Includes index signature comments #2414", () => { + it("#2414 Includes index signature comments ", () => { const project = convert(); equal( Comment.combineDisplayParts( - query(project, "ObjectWithIndexSignature").indexSignature + query(project, "ObjectWithIndexSignature").indexSignatures?.[0] ?.comment?.summary, ), "Index comment.", ); }); - it("Handles destructured object parameter defaults, #2430", () => { + it("#2430 Handles destructured object parameter defaults, ", () => { const project = convert(); const Checkbox = querySig(project, "Checkbox"); equal(Checkbox.parameters?.length, 1); equal(Checkbox.parameters[0].name, "props"); const type = Checkbox.parameters[0].type; equal(type?.type, "reflection"); - equal(type.declaration.children?.map((c) => c.name), [ - "falseValue", - "trueValue", - "value", - ]); - equal(type.declaration.children?.map((c) => c.defaultValue), [ - "false", - "true", - undefined, - ]); + equal( + type.declaration.children?.map((c) => c.name), + ["falseValue", "trueValue", "value"], + ); + equal( + type.declaration.children.map((c) => c.defaultValue), + ["false", "true", undefined], + ); }); - it("Handles function-namespaces created with Object.assign #2436", () => { + it("#2436 Handles function-namespaces created with Object.assign ", () => { const project = convert(); equal(query(project, "bug").kind, ReflectionKind.Function); const foo = query(project, "bug.foo"); @@ -1302,17 +1333,17 @@ describe("Issue Tests", () => { equal(bar.kind, ReflectionKind.Property, "property"); }); - it("Does not warn due to the diamond problem in comment discovery #2437", () => { + it("#2437 Does not warn due to the diamond problem in comment discovery ", () => { convert(); logger.expectNoOtherMessages(); }); - it("Handles recursive aliases without looping infinitely #2438", () => { + it("#2438 Handles recursive aliases without looping infinitely ", () => { const bad = query(convert(), "Bad"); equal(bad.kind, ReflectionKind.Interface); }); - it("Handles transient symbols correctly, #2444", () => { + it("#2444 Handles transient symbols correctly, ", () => { const project = convert(); const boolEq = query(project, "Boolean.equal"); const numEq = query(project, "Number.equal"); @@ -1320,14 +1351,14 @@ describe("Issue Tests", () => { equal(numEq.signatures![0].parameters![0].type?.toString(), "number"); }); - it("Handles unions created due to union within intersection, #2451", () => { + it("#2451 Handles unions created due to union within intersection, ", () => { const project = convert(); const is = querySig(project, "FooA.is"); equal(is.type?.toString(), "this is Foo & Object"); }); - it("Does not care about conversion order for @link resolution, #2466", () => { + it("#2466 Does not care about conversion order for @link resolution, ", () => { const project = convert(); const Two = query(project, "Two"); @@ -1347,29 +1378,41 @@ describe("Issue Tests", () => { ]); }); - it("Creates a separate namespace for `declare namespace` case #2476", () => { + it("#2476 Creates a separate namespace for `declare namespace` case ", () => { const project = convert(); - equal(project.children?.map((c) => [c.name, c.kind]), [ - ["test", ReflectionKind.Namespace], - ["test", ReflectionKind.Function], - ]); + equal( + project.children?.map((c) => [c.name, c.kind]), + [ + ["test", ReflectionKind.Namespace], + ["test", ReflectionKind.Function], + ], + ); - equal(project.children[0].children?.map((c) => c.name), ["Options"]); + equal( + project.children[0].children?.map((c) => c.name), + ["Options"], + ); }); - it("Creates a separate namespace for `declare namespace` case with variables #2478", () => { + it("#2478 Creates a separate namespace for `declare namespace` case with variables ", () => { const project = convert(); - equal(project.children?.map((c) => [c.name, c.kind]), [ - ["test", ReflectionKind.Namespace], - ["test", ReflectionKind.Function], - ]); + equal( + project.children?.map((c) => [c.name, c.kind]), + [ + ["test", ReflectionKind.Namespace], + ["test", ReflectionKind.Function], + ], + ); - equal(project.children[0].children?.map((c) => c.name), ["Options"]); + equal( + project.children[0].children?.map((c) => c.name), + ["Options"], + ); }); - it("Does not crash when rendering recursive hierarchy, #2495", () => { + it("#2495 Does not crash when rendering recursive hierarchy, ", () => { const project = convert(); const theme = new DefaultTheme(app.renderer); @@ -1379,18 +1422,18 @@ describe("Issue Tests", () => { context.hierarchyTemplate(page); }); - it("Correctly cleans up references to functions #2496", () => { + it("#2496 Correctly cleans up references to functions ", () => { app.options.setValue("excludeNotDocumented", true); convert(); }); - it("Sorts literal numeric unions when converting a type, #2502", () => { + it("#2502 Sorts literal numeric unions when converting a type, ", () => { const project = convert(); const refl = query(project, "Test"); equal(refl.type?.toString(), "1 | 2 | 3"); }); - it("Handles an infinitely recursive type, #2507", () => { + it("#2507 Handles an infinitely recursive type, ", () => { const project = convert(); const type = querySig(project, "fromPartial").typeParameters![0].type; @@ -1408,7 +1451,7 @@ describe("Issue Tests", () => { equal(type?.toString(), "Value & Object"); }); - it("Handles constructed references to enumeration types, #2508", () => { + it("#2508 Handles constructed references to enumeration types, ", () => { const project = convert(); const refl = query(project, "Bar.color"); equal(refl.type?.type, "reference"); @@ -1416,7 +1459,7 @@ describe("Issue Tests", () => { equal(refl.type.reflection?.id, query(project, "Color").id); }); - it("Does not duplicate comments due to signatures being present, #2509", () => { + it("#2509 Does not duplicate comments due to signatures being present, ", () => { const project = convert(); const cb = query(project, "Int.cb"); equal(Comment.combineDisplayParts(cb.comment?.summary), "Cb"); @@ -1430,4 +1473,167 @@ describe("Issue Tests", () => { equal(cb2.type?.type, "reflection"); equal(cb2.type.declaration.signatures![0].comment, undefined); }); + + it("#2521 Specifying comment on variable still inherits signature comments, ", () => { + const project = convert(); + + equal(getComment(project, "fooWithoutComment"), ""); + equal(getSigComment(project, "fooWithoutComment", 0), "Overload 1"); + equal(getSigComment(project, "fooWithoutComment", 1), "Overload 2"); + + equal(getComment(project, "fooWithComment"), "New comment."); + equal(getSigComment(project, "fooWithComment", 0), "Overload 1"); + equal(getSigComment(project, "fooWithComment", 1), "Overload 2"); + }); + + it("#2545 discovers comments from non-exported 'parent' methods", () => { + const project = convert(); + + equal(getSigComment(project, "Child.notAbstract"), "notAbstract docs"); + equal( + getSigComment(project, "Child.notAbstract2"), + "notAbstract2 docs", + ); + equal(getSigComment(project, "Child.isAbstract"), "isAbstract docs"); + equal( + getComment(project, "Child.abstractProperty"), + "abstractProperty docs", + ); + + // #2084 + equal( + querySig(project, "Bar.isInternal").comment?.hasModifier( + "@internal", + ), + true, + ); + }); + + it("#2552 Ignores @license and @import comments, ", () => { + const project = convert(); + equal( + Comment.combineDisplayParts(project.comment?.summary), + "This is an awesome module.", + ); + equal(getComment(project, "something"), ""); + }); + + it("#2553 Does not warn about documented constructor signature type aliases, ", () => { + const project = convert(); + app.validate(project); + logger.expectNoOtherMessages(); + }); + + it("#2574 default export", () => { + const project = convert(); + const sig = querySig(project, "usesDefaultExport"); + const param = sig.parameters?.[0]; + ok(param, "Missing parameter"); + equal(param.name, "param", "Incorrect parameter name"); + equal( + param.type?.type, + "reference", + "Parameter is not a reference type", + ); + equal(param.type.name, "DefaultExport", "Incorrect reference name"); + equal(param.type.qualifiedName, "default", "Incorrect qualified name"); + }); + + it("#2574 not default export", () => { + const project = convert(); + const sig = querySig(project, "usesNonDefaultExport"); + const param = sig.parameters?.[0]; + ok(param, "Missing parameter"); + equal(param.name, "param", "Incorrect parameter name"); + equal( + param.type?.type, + "reference", + "Parameter is not a reference type", + ); + equal(param.type.name, "NotDefaultExport", "Incorrect reference name"); + equal( + param.type.qualifiedName, + "NotDefaultExport", + "Incorrect qualified name", + ); + }); + + it("#2582 nested @namespace", () => { + const project = convert(); + + equalKind(query(project, "f32"), ReflectionKind.Namespace); + equalKind(query(project, "f32.a"), ReflectionKind.Namespace); + equalKind(query(project, "f32.a.member"), ReflectionKind.Variable); + equalKind(query(project, "f32.a.fn"), ReflectionKind.Function); + equalKind(query(project, "f32.b"), ReflectionKind.Namespace); + equalKind(query(project, "f32.b.member"), ReflectionKind.Reference); // Somewhat odd, but not wrong... + equalKind(query(project, "f32.b.fn"), ReflectionKind.Function); + + equal(getComment(project, "f32"), "f32 comment"); + equal(getComment(project, "f32.a"), "A comment"); + equal(getComment(project, "f32.a.member"), "Member comment"); + equal(getComment(project, "f32.a.fn"), "Fn comment"); + equal(getComment(project, "f32.b"), "B comment"); + }); + + it("#2585 supports comments on union members", () => { + const project = convert(); + const Foo = query(project, "Foo"); + equal(Foo.type?.type, "union"); + + equal(Foo.type.elementSummaries?.length, 2); + equal(Foo.type.elementSummaries.map(Comment.combineDisplayParts), [ + "Doc of foo1.", + "Doc of foo2.", + ]); + }); + + it("#2587 comment on shorthand property declaration", () => { + const project = convert(); + + const sig = querySig(project, "foo"); + equal(sig.type?.type, "reflection"); + const x = sig.type.declaration.getChildByName("x"); + ok(x); + + equal( + Comment.combineDisplayParts(x.comment?.summary), + "Shorthand comment", + ); + }); + + it("#2603 handles @author tag", () => { + const project = convert(); + const x = query(project, "x"); + equal( + x.comment?.getTag("@author"), + new CommentTag("@author", [{ kind: "text", text: "Ian Awesome" }]), + ); + + logger.expectNoOtherMessages(); + }); + + it("#2611 can suppress warnings from comments in declaration files", () => { + convert(); + logger.expectMessage( + "warn: Encountered an unknown block tag @tagThatIsNotDefined", + ); + logger.expectNoOtherMessages(); + logger.reset(); + + app.options.setValue("suppressCommentWarningsInDeclarationFiles", true); + convert(); + logger.expectNoOtherMessages(); + }); + + it("#2614 supports @since tag", () => { + const project = convert(); + const foo = querySig(project, "foo"); + equal( + foo.comment?.getTag("@since"), + new CommentTag("@since", [{ kind: "text", text: "1.0.0" }]), + ); + + logger.expectNoOtherMessages(); + }); }); diff --git a/src/test/merge.test.ts b/src/test/merge.test.ts index e4c023564..de89b76a4 100644 --- a/src/test/merge.test.ts +++ b/src/test/merge.test.ts @@ -2,7 +2,7 @@ import { deepStrictEqual as equal, ok } from "assert"; import { join } from "path"; import { Application, - DeclarationReflection, + type DeclarationReflection, EntryPointStrategy, ReferenceType, } from "../index"; @@ -27,7 +27,10 @@ describe("Merging projects", () => { logger.expectNoOtherMessages(); equal(project?.name, "typedoc"); - equal(project.children?.map((c) => c.name), ["alias", "class"]); + equal( + project.children?.map((c) => c.name), + ["alias", "class"], + ); const crossRef = project.getChildByName( "alias.MergedCrossReference", diff --git a/src/test/models/comment.test.ts b/src/test/models/comment.test.ts index 3cbdb735a..255b8ddef 100644 --- a/src/test/models/comment.test.ts +++ b/src/test/models/comment.test.ts @@ -1,5 +1,5 @@ import { deepStrictEqual as equal } from "assert"; -import { Comment, CommentDisplayPart } from "../../index"; +import { Comment, type CommentDisplayPart } from "../../index"; describe("Comment.combineDisplayParts", () => { it("Handles text and code", () => { diff --git a/src/test/models/types.test.ts b/src/test/models/types.test.ts index 21c1dff5b..35efedacf 100644 --- a/src/test/models/types.test.ts +++ b/src/test/models/types.test.ts @@ -3,6 +3,7 @@ import * as T from "../../lib/models/types"; import { strictEqual as equal } from "assert"; import { ProjectReflection } from "../../lib/models"; +import { FileRegistry } from "../../lib/models/FileRegistry"; describe("Type.toString", () => { describe("Union types", () => { @@ -262,7 +263,7 @@ describe("Type.toString", () => { }); it("Does not wrap type query", () => { - const project = new ProjectReflection("test"); + const project = new ProjectReflection("test", new FileRegistry()); const type = new T.OptionalType( new T.QueryType( T.ReferenceType.createResolvedReference("X", -1, project), @@ -283,7 +284,7 @@ describe("Type.toString", () => { describe("Type operator", () => { it("Does not wrap type query", () => { - const project = new ProjectReflection("test"); + const project = new ProjectReflection("test", new FileRegistry()); const type = new T.TypeOperatorType( new T.QueryType( T.ReferenceType.createResolvedReference("X", -1, project), @@ -327,14 +328,3 @@ describe("Type.toString", () => { }); }); }); - -describe("Union Types", () => { - it("Normalizes true | false to boolean", () => { - const type = new T.UnionType([ - new T.LiteralType(true), - new T.LiteralType(123), - new T.LiteralType(false), - ]); - equal(type.toString(), "boolean | 123"); - }); -}); diff --git a/src/test/packages.test.ts b/src/test/packages.test.ts index e40a568a3..6b10befc5 100644 --- a/src/test/packages.test.ts +++ b/src/test/packages.test.ts @@ -8,11 +8,7 @@ import { TestLogger } from "./TestLogger"; import { createMinimatch } from "../lib/utils/paths"; describe("Packages support", () => { - let project: ReturnType<typeof tempdirProject>; - - beforeEach(() => { - project = tempdirProject(); - }); + using project = tempdirProject(); afterEach(() => { project.rm(); diff --git a/src/test/programs.ts b/src/test/programs.ts index 63055f0ea..0ceda952f 100644 --- a/src/test/programs.ts +++ b/src/test/programs.ts @@ -2,9 +2,9 @@ import { deepStrictEqual as equal } from "assert"; import { join } from "path"; import ts from "typescript"; import { - Application, + type Application, EntryPointStrategy, - JSONOutput, + type JSONOutput, ProjectReflection, SourceReference, TSConfigReader, @@ -28,6 +28,7 @@ export function getConverterApp() { name: "typedoc", excludeExternals: true, disableSources: false, + excludePrivate: false, tsconfig: join(getConverterBase(), "tsconfig.json"), externalPattern: ["**/node_modules/**"], plugin: [], diff --git a/src/test/slow/entry-point.test.ts b/src/test/slow/entry-point.test.ts index 14b7837ea..c44f805af 100644 --- a/src/test/slow/entry-point.test.ts +++ b/src/test/slow/entry-point.test.ts @@ -1,20 +1,24 @@ -import { tempdirProject } from "@typestrong/fs-fixture-builder"; +import { type Project, tempdirProject } from "@typestrong/fs-fixture-builder"; import { deepStrictEqual as equal, ok } from "assert"; import { join } from "path"; import { Application, EntryPointStrategy } from "../.."; -const fixture = tempdirProject(); -fixture.addJsonFile("tsconfig.json", { - include: ["."], -}); -fixture.addJsonFile("package.json", { - main: "index.ts", -}); -fixture.addFile("index.ts", "export function fromIndex() {}"); -fixture.addFile("extra.ts", "export function extra() {}"); - describe("Entry Points", () => { + let fixture: Project; + let tsconfig: string; + beforeEach(() => { + fixture = tempdirProject(); + tsconfig = join(fixture.cwd, "tsconfig.json"); + + fixture.addJsonFile("tsconfig.json", { + include: ["."], + }); + fixture.addJsonFile("package.json", { + main: "index.ts", + }); + fixture.addFile("index.ts", "export function fromIndex() {}"); + fixture.addFile("extra.ts", "export function extra() {}"); fixture.write(); }); @@ -22,8 +26,6 @@ describe("Entry Points", () => { fixture.rm(); }); - const tsconfig = join(fixture.cwd, "tsconfig.json"); - it("Supports expanding existing paths", async () => { const app = await Application.bootstrap({ tsconfig, diff --git a/src/test/slow/internationalization-usage.test.ts b/src/test/slow/internationalization-usage.test.ts new file mode 100644 index 000000000..f9f6a686c --- /dev/null +++ b/src/test/slow/internationalization-usage.test.ts @@ -0,0 +1,85 @@ +import ts from "typescript"; +import { ok } from "assert/strict"; +import { Logger, Options, TSConfigReader } from "../.."; +import { join } from "path"; +import { existsSync, readFileSync } from "fs"; +import { Internationalization } from "../../lib/internationalization/internationalization"; + +describe("Internationalization", () => { + it("Does not include strings in translatable object which are unused", () => { + const options = new Options(new Internationalization(null).proxy); + const tsconfigReader = new TSConfigReader(); + tsconfigReader.read(options, new Logger(), process.cwd()); + + const translatableTs = join( + __dirname, + "../../lib/internationalization/translatable.ts", + ); + + const host: ts.LanguageServiceHost = { + getScriptFileNames: () => options.getFileNames().slice(), + getScriptVersion: () => "unused", + getScriptSnapshot: (fileName) => { + if (!existsSync(fileName)) return undefined; + return ts.ScriptSnapshot.fromString( + readFileSync(fileName, "utf-8"), + ); + }, + getCurrentDirectory: () => process.cwd(), + getCompilationSettings: () => options.getCompilerOptions(), + getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts), + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, + directoryExists: ts.sys.directoryExists, + getDirectories: ts.sys.getDirectories, + }; + + const service = ts.createLanguageService( + host, + ts.createDocumentRegistry(), + ); + + const program = service.getProgram(); + ok(program, "Failed to get program for i18n analysis"); + + const sf = program.getSourceFile(translatableTs); + ok(sf, "Failed to get source file"); + + const moduleSymbol = program.getTypeChecker().getSymbolAtLocation(sf)!; + const translatable = program + .getTypeChecker() + .tryGetMemberInModuleExports("translatable", moduleSymbol); + ok(translatable, "Failed to get translatable symbol"); + + ok(ts.isVariableDeclaration(translatable.valueDeclaration!)); + ok(ts.isAsExpression(translatable.valueDeclaration.initializer!)); + ok( + ts.isObjectLiteralExpression( + translatable.valueDeclaration.initializer.expression, + ), + ); + const translatableObj = + translatable.valueDeclaration.initializer.expression; + + translatableObj.forEachChild((child) => { + ok(ts.isPropertyAssignment(child)); + const refs = service.getReferencesAtPosition( + sf.fileName, + child.getStart(), + ); + const refCount = + refs?.filter( + (ref) => + !/locales\/.*\.cts$/.test(ref.fileName) && + !ref.fileName.endsWith("translatable.ts"), + ).length ?? 0; + ok( + refCount, + `Translatable key ${child.name.getText()} is not referenced.`, + ); + }); + + service.dispose(); + }); +}); diff --git a/src/test/utils.ts b/src/test/utils.ts index 53a630372..c1dec3e2c 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -2,12 +2,13 @@ import { ok } from "assert"; import { Comment, DeclarationReflection, - ProjectReflection, + type ProjectReflection, Reflection, ReflectionKind, - SignatureReflection, + type SignatureReflection, } from ".."; import { filterMap } from "../lib/utils"; +import { equal } from "assert/strict"; export function query( project: ProjectReflection, @@ -35,6 +36,16 @@ export function getComment(project: ProjectReflection, name: string) { return Comment.combineDisplayParts(query(project, name).comment?.summary); } +export function getSigComment( + project: ProjectReflection, + name: string, + index = 0, +) { + return Comment.combineDisplayParts( + querySig(project, name, index).comment?.summary, + ); +} + export function getLinks(refl: Reflection): Array<{ display: string; target: undefined | string | [ReflectionKind, string]; @@ -58,3 +69,11 @@ export function getLinks(refl: Reflection): Array<{ } }); } + +export function equalKind(refl: Reflection, kind: ReflectionKind) { + equal( + refl.kind, + kind, + `Expected ${ReflectionKind[kind]} but got ${ReflectionKind[refl.kind]}`, + ); +} diff --git a/src/test/utils/fs.test.ts b/src/test/utils/fs.test.ts index 3c517c6e8..0d0a95133 100644 --- a/src/test/utils/fs.test.ts +++ b/src/test/utils/fs.test.ts @@ -1,20 +1,11 @@ import * as fs from "fs"; import { createServer } from "net"; -import { Project, tempdirProject } from "@typestrong/fs-fixture-builder"; -import { AssertionError, deepStrictEqual as equal } from "assert"; +import { type Project, tempdirProject } from "@typestrong/fs-fixture-builder"; +import { type AssertionError, deepStrictEqual as equal } from "assert"; import { basename, dirname, resolve, normalize } from "path"; import { getCommonDirectory, glob } from "../../lib/utils/fs"; describe("fs.ts", () => { - let fix: Project; - beforeEach(() => { - fix = tempdirProject(); - }); - - afterEach(() => { - fix.rm(); - }); - describe("getCommonDirectory", () => { it("Returns the empty string if no files are provided", () => { equal(getCommonDirectory([]), ""); @@ -38,6 +29,14 @@ describe("fs.ts", () => { }); describe("glob", () => { + let fix: Project; + beforeEach(() => { + fix = tempdirProject(); + }); + afterEach(() => { + fix.rm(); + }); + it("handles root match", () => { fix.write(); diff --git a/src/test/utils/html.test.ts b/src/test/utils/html.test.ts index af0180f77..ddb2c7b44 100644 --- a/src/test/utils/html.test.ts +++ b/src/test/utils/html.test.ts @@ -1,32 +1,281 @@ -import { strictEqual as equal } from "assert"; -import { getTextContent } from "../../lib/utils/html"; +import { deepStrictEqual as equal } from "assert"; +import { HtmlAttributeParser, ParserState } from "../../lib/utils/html"; -describe("getTextContent", () => { - it("Handles simple text", () => { - equal(getTextContent("Hello there"), "Hello there"); +describe("HtmlAttributeParser", () => { + enum State { + BeforeAttributeName = "BeforeAttributeName", + AfterAttributeName = "AfterAttributeName", + BeforeAttributeValue = "BeforeAttributeValue", + } + + function stateStr(state: ParserState) { + return { + [ParserState.BeforeAttributeName]: State.BeforeAttributeName, + [ParserState.AfterAttributeName]: State.AfterAttributeName, + [ParserState.BeforeAttributeValue]: State.BeforeAttributeValue, + [ParserState.END]: "<END>", + }[state]; + } + + function parseWithEnd(text: string) { + const parser = new HtmlAttributeParser(text); + const data = []; + do { + parser.step(); + data.push([ + stateStr(parser.state), + parser.currentAttributeName, + parser.currentAttributeValue, + ]); + } while (parser.state != ParserState.END); + return data; + } + + function parse(text: string) { + const data = parseWithEnd(text); + data.pop(); + return data; + } + + function parseAttrsToObject(text: string) { + const result: Record<string, string> = {}; + for (const elem of parseWithEnd(text)) { + result[elem[1]] = elem[2]; + } + delete result[""]; + return result; + } + + it("Handles self closing tag", () => { + equal(parse(" >"), []); + + equal(parse(" />"), []); + }); + + it("Handles EOF before end of tag", () => { + equal(parse(" \t\f"), []); + }); + + it("Handles names without values", () => { + equal(parse("a b c />"), [ + [State.AfterAttributeName, "a", ""], + [State.AfterAttributeName, "b", ""], + [State.AfterAttributeName, "c", ""], + ]); + }); + + it("Handles unquoted value", () => { + equal(parse("a=1 b=bbb />"), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "1"], + [State.BeforeAttributeValue, "b", ""], + [State.BeforeAttributeName, "b", "bbb"], + ]); + }); + + it("Handles single quoted value", () => { + equal(parse("a='1' b='b b' />"), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "1"], + [State.BeforeAttributeValue, "b", ""], + [State.BeforeAttributeName, "b", "b b"], + ]); + }); + + it("Handles double quoted value", () => { + equal(parse('a="1" b="b b" />'), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "1"], + [State.BeforeAttributeValue, "b", ""], + [State.BeforeAttributeName, "b", "b b"], + ]); + }); + + it("Handles named escapes", () => { + equal(parse('a="&" b="&" />'), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "&"], + [State.BeforeAttributeValue, "b", ""], + [State.BeforeAttributeName, "b", "&"], + ]); + }); + + it("Handles invalid named escape", () => { + equal(parse('a="&ZZBADESCAPE;" />'), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "&ZZBADESCAPE;"], + ]); + }); + + it("Handles an attribute without a name", () => { + equal(parse(" =oops >"), [ + [State.BeforeAttributeValue, "", ""], + [State.BeforeAttributeName, "", "oops"], + ]); + }); + + it("Handles invalid characters in attribute names", () => { + equal(parse(" a\" a' a< >"), [ + [State.AfterAttributeName, 'a"', ""], + [State.AfterAttributeName, "a'", ""], + [State.AfterAttributeName, "a<", ""], + ]); + }); + + it("Handles missing attribute value", () => { + equal(parse(" a= \t\n\f>"), [[State.BeforeAttributeValue, "a", ""]]); + }); + + it("Handles a null character in a double quoted attribute value", () => { + equal(parse(' a="\0" >'), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "\ufffd"], + ]); + }); + + it("Handles an unterminated double quoted attribute value", () => { + equal(parse(' a="x'), [[State.BeforeAttributeValue, "a", ""]]); }); - it("Handles entity escapes", () => { - equal(getTextContent("A B"), "A B"); - equal(getTextContent("A B"), "A B"); - equal(getTextContent("A B"), "A B"); - equal(getTextContent("A B"), "A B"); - equal(getTextContent("A & B"), "A & B"); + it("Handles missing attribute name after an attribute ", () => { + equal(parse(" a \t\n\f =>"), [ + [State.AfterAttributeName, "a", ""], + [State.BeforeAttributeValue, "a", ""], + ]); + }); + + it("Handles named escapes in single quoted value", () => { + equal(parse("a='&' b='&' c='&oops' />"), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "&"], + [State.BeforeAttributeValue, "b", ""], + [State.BeforeAttributeName, "b", "&"], + [State.BeforeAttributeValue, "c", ""], + [State.BeforeAttributeName, "c", "&oops"], + ]); + }); + + it("Handles a null character in a single quoted attribute value", () => { + equal(parse(" a='\0' >"), [ + [State.BeforeAttributeValue, "a", ""], + [State.BeforeAttributeName, "a", "\ufffd"], + ]); + }); + + it("Handles an unterminated single quoted attribute value", () => { + equal(parse(" a='x"), [[State.BeforeAttributeValue, "a", ""]]); + }); + + it("Properly terminates unquoted attributes", () => { + equal(parseAttrsToObject(" a=a\t b=b\n c=c\f"), { + a: "a", + b: "b", + c: "c", + }); + }); + + it("Handles character references in unquoted attributes", () => { + equal(parseAttrsToObject(" a=&a b=& c=&oops >"), { + a: "&a", + b: "&", + c: "&oops", + }); + }); + + it("Handles more unquoted attribute cases", () => { + equal(parseAttrsToObject(" a=a>"), { + a: "a", + }); + + equal(parseAttrsToObject(" a=\0x>"), { + a: "\ufffdx", + }); + + equal(parseAttrsToObject(" a=a\"'<=`>"), { + a: "a\"'<=`", + }); + + equal(parseAttrsToObject(" a=a"), { + a: "a", + }); + }); + + it("Handles characters after a quoted attribute", () => { + equal(parseAttrsToObject(" a='a'\tb='b'\nc='c'\fd='d'>"), { + a: "a", + b: "b", + c: "c", + d: "d", + }); + + equal(parseAttrsToObject(" a='a'/"), { + a: "a", + }); + + equal(parseAttrsToObject(" a='a'"), { + a: "a", + }); + + equal(parseAttrsToObject(" a='a'b='b' "), { + a: "a", + b: "b", + }); + }); + + it("Handles simple numeric character references", () => { + equal(parseAttrsToObject("a=a b=Z c=Z"), { + a: "a", + b: "Z", + c: "Z", + }); + }); + + it("Handles an invalid character reference", () => { + equal(parseAttrsToObject("a=&a"), { + a: "&a", + }); + }); + + it("Handles an invalid decimal character reference start", () => { + equal(parseAttrsToObject("a=&#;"), { + a: "&#;", + }); + }); + + it("Handles an invalid hex character reference", () => { + equal(parseAttrsToObject("a=&#x;"), { + a: "&#x;", + }); + + equal(parseAttrsToObject("a=Z>"), { + a: "Z", + }); }); - it("Strips HTML tags", () => { - equal(getTextContent("A <span>B</span> C"), "A B C"); - equal(getTextContent("A <span a=b>B</span> C"), "A B C"); - equal(getTextContent("A <span a='b'>B</span> C"), "A B C"); - equal(getTextContent('A <span a="b">B</span> C'), "A B C"); - equal(getTextContent('A <span a="b"'), "A "); + it("Handles an ambiguous ampersand without a trailing alphanumeric", () => { + equal(parseAttrsToObject("a=&a"), { + a: "&a", + }); }); - it("Handles nested HTML tags", () => { - equal(getTextContent("A<span>B<span>C</span></span>"), "ABC"); + it("Handles an invalid decimal character reference end", () => { + equal(parseAttrsToObject("a=a>"), { + a: "a", + }); }); - it("Preserves lt/gt", () => { - equal(getTextContent("<a>"), "<a>"); + it("Handles invalid characters in numeric character references", () => { + equal(parseAttrsToObject("a='nul:�x'>"), { + a: "nul:\ufffdx", + }); + equal(parseAttrsToObject("a='rng:�x'>"), { + a: "rng:\ufffdx", + }); + equal(parseAttrsToObject("a='leading surrogate:�x'>"), { + a: "leading surrogate:\ufffdx", + }); + equal(parseAttrsToObject("a='trailing surrogate:�x'>"), { + a: "trailing surrogate:\ufffdx", + }); }); }); diff --git a/src/test/utils/minimalSourceFile.test.ts b/src/test/utils/minimalSourceFile.test.ts index a45c07d3a..f38a6376b 100644 --- a/src/test/utils/minimalSourceFile.test.ts +++ b/src/test/utils/minimalSourceFile.test.ts @@ -24,4 +24,13 @@ describe("MinimalSourceFile", () => { check("4", { line: 2, character: 0 }); check("5", { line: 3, character: 0 }); }); + + it("#2605 Should handle multiple consecutive newlines", () => { + const sf = new MinimalSourceFile("a\n\nb", ""); + + equal(sf.getLineAndCharacterOfPosition(0), { line: 0, character: 0 }); // a + equal(sf.getLineAndCharacterOfPosition(1), { line: 0, character: 1 }); // \n + equal(sf.getLineAndCharacterOfPosition(2), { line: 1, character: 0 }); // \n + equal(sf.getLineAndCharacterOfPosition(3), { line: 2, character: 0 }); // b + }); }); diff --git a/src/test/utils/options/declaration.test.ts b/src/test/utils/options/declaration.test.ts index 953794063..cfc57f66e 100644 --- a/src/test/utils/options/declaration.test.ts +++ b/src/test/utils/options/declaration.test.ts @@ -1,56 +1,63 @@ import { deepStrictEqual as equal, ok, throws } from "assert"; import { join, resolve } from "path"; import { - ArrayDeclarationOption, + type ArrayDeclarationOption, convert, - DeclarationOption, + type DeclarationOption, getDefaultValue, - MapDeclarationOption, - MixedDeclarationOption, - ObjectDeclarationOption, - NumberDeclarationOption, + type MapDeclarationOption, + type MixedDeclarationOption, + type ObjectDeclarationOption, + type NumberDeclarationOption, ParameterType, - StringDeclarationOption, + type StringDeclarationOption, } from "../../../lib/utils/options/declaration"; +import { Internationalization } from "../../../lib/internationalization/internationalization"; + +const emptyHelp = () => ""; describe("Options - conversions", () => { + const i18n = new Internationalization(null).proxy; const optionWithType = (type: ParameterType) => ({ type, defaultValue: undefined, name: "test", - help: "", + help: emptyHelp, }) as DeclarationOption; it("Converts to numbers", () => { - equal(convert("123", optionWithType(ParameterType.Number), ""), 123); - equal(convert("a", optionWithType(ParameterType.Number), ""), 0); - equal(convert(NaN, optionWithType(ParameterType.Number), ""), 0); + equal( + convert("123", optionWithType(ParameterType.Number), i18n, ""), + 123, + ); + equal(convert("a", optionWithType(ParameterType.Number), i18n, ""), 0); + equal(convert(NaN, optionWithType(ParameterType.Number), i18n, ""), 0); }); it("Converts to number if value is the lowest allowed value for a number option", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, minValue: 1, maxValue: 10, defaultValue: 1, }; - equal(convert(1, declaration, ""), 1); + equal(convert(1, declaration, i18n, ""), 1); }); it("Generates an error if value is too low for a number option", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, minValue: 1, maxValue: 10, defaultValue: 1, }; throws( - () => convert(0, declaration, ""), + () => convert(0, declaration, i18n, ""), new Error("test must be between 1 and 10"), ); }); @@ -58,54 +65,54 @@ describe("Options - conversions", () => { it("Generates an error if value is too low for a number option with no max", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, minValue: 1, defaultValue: 1, }; throws( - () => convert(0, declaration, ""), - new Error("test must be >= 1"), + () => convert(0, declaration, i18n, ""), + new Error("test must be equal to or greater than 1"), ); }); it("Generates an error if value is too high for a number option with no min", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, maxValue: 10, defaultValue: 1, }; throws( - () => convert(11, declaration, ""), - new Error("test must be <= 10"), + () => convert(11, declaration, i18n, ""), + new Error("test must be less than or equal to 10"), ); }); it("Converts to number if value is the highest allowed value for a number option", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, minValue: 1, maxValue: 10, defaultValue: 1, }; - equal(convert(10, declaration, ""), 10); + equal(convert(10, declaration, i18n, ""), 10); }); it("Generates an error if value is too high for a number option", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, minValue: 1, maxValue: 10, defaultValue: 1, }; throws( - () => convert(11, declaration, ""), + () => convert(11, declaration, i18n, ""), new Error("test must be between 1 and 10"), ); }); @@ -113,7 +120,7 @@ describe("Options - conversions", () => { it("Validates number options", () => { const declaration: NumberDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Number, validate: (value: number) => { if (value % 2 !== 0) { @@ -121,30 +128,42 @@ describe("Options - conversions", () => { } }, }; - equal(convert(0, declaration, ""), 0); - equal(convert(2, declaration, ""), 2); - equal(convert(4, declaration, ""), 4); + equal(convert(0, declaration, i18n, ""), 0); + equal(convert(2, declaration, i18n, ""), 2); + equal(convert(4, declaration, i18n, ""), 4); throws( - () => convert(1, declaration, ""), + () => convert(1, declaration, i18n, ""), new Error("test must be even"), ); }); it("Converts to strings", () => { - equal(convert("123", optionWithType(ParameterType.String), ""), "123"); - equal(convert(123, optionWithType(ParameterType.String), ""), "123"); equal( - convert(["1", "2"], optionWithType(ParameterType.String), ""), + convert("123", optionWithType(ParameterType.String), i18n, ""), + "123", + ); + equal( + convert(123, optionWithType(ParameterType.String), i18n, ""), + "123", + ); + equal( + convert(["1", "2"], optionWithType(ParameterType.String), i18n, ""), "1,2", ); - equal(convert(null, optionWithType(ParameterType.String), ""), ""); - equal(convert(void 0, optionWithType(ParameterType.String), ""), ""); + equal( + convert(null, optionWithType(ParameterType.String), i18n, ""), + "", + ); + equal( + convert(void 0, optionWithType(ParameterType.String), i18n, ""), + "", + ); }); it("Validates string options", () => { const declaration: StringDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.String, validate: (value: string) => { if (value !== value.toUpperCase()) { @@ -152,48 +171,83 @@ describe("Options - conversions", () => { } }, }; - equal(convert("TOASTY", declaration, ""), "TOASTY"); + equal(convert("TOASTY", declaration, i18n, ""), "TOASTY"); throws( - () => convert("toasty", declaration, ""), + () => convert("toasty", declaration, i18n, ""), new Error("test must be upper case"), ); }); it("Converts to booleans", () => { - equal(convert("a", optionWithType(ParameterType.Boolean), ""), true); - equal(convert([1], optionWithType(ParameterType.Boolean), ""), true); - equal(convert(false, optionWithType(ParameterType.Boolean), ""), false); + equal( + convert("a", optionWithType(ParameterType.Boolean), i18n, ""), + true, + ); + equal( + convert([1], optionWithType(ParameterType.Boolean), i18n, ""), + true, + ); + equal( + convert(false, optionWithType(ParameterType.Boolean), i18n, ""), + false, + ); }); it("Converts to arrays", () => { - equal(convert("12,3", optionWithType(ParameterType.Array), ""), [ - "12,3", - ]); - equal(convert(["12,3"], optionWithType(ParameterType.Array), ""), [ + equal(convert("12,3", optionWithType(ParameterType.Array), i18n, ""), [ "12,3", ]); - equal(convert(true, optionWithType(ParameterType.Array), ""), []); + equal( + convert(["12,3"], optionWithType(ParameterType.Array), i18n, ""), + ["12,3"], + ); + equal(convert(true, optionWithType(ParameterType.Array), i18n, ""), []); - equal(convert("/,a", optionWithType(ParameterType.PathArray), ""), [ - resolve("/,a"), - ]); - equal(convert(["/foo"], optionWithType(ParameterType.PathArray), ""), [ - resolve("/foo"), - ]); - equal(convert(true, optionWithType(ParameterType.PathArray), ""), []); + equal( + convert("/,a", optionWithType(ParameterType.PathArray), i18n, ""), + [resolve("/,a")], + ); + equal( + convert( + ["/foo"], + optionWithType(ParameterType.PathArray), + i18n, + "", + ), + [resolve("/foo")], + ); + equal( + convert(true, optionWithType(ParameterType.PathArray), i18n, ""), + [], + ); - equal(convert("a,b", optionWithType(ParameterType.ModuleArray), ""), [ - "a,b", - ]); - equal(convert(["a,b"], optionWithType(ParameterType.ModuleArray), ""), [ - "a,b", - ]); - equal(convert(true, optionWithType(ParameterType.ModuleArray), ""), []); + equal( + convert("a,b", optionWithType(ParameterType.ModuleArray), i18n, ""), + ["a,b"], + ); + equal( + convert( + ["a,b"], + optionWithType(ParameterType.ModuleArray), + i18n, + "", + ), + ["a,b"], + ); + equal( + convert(true, optionWithType(ParameterType.ModuleArray), i18n, ""), + [], + ); }); it("ModuleArray is resolved if relative", () => { equal( - convert(["./foo"], optionWithType(ParameterType.ModuleArray), ""), + convert( + ["./foo"], + optionWithType(ParameterType.ModuleArray), + i18n, + "", + ), [join(process.cwd(), "foo")], ); }); @@ -201,7 +255,7 @@ describe("Options - conversions", () => { it("Validates array options", () => { const declaration: ArrayDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Array, validate: (value: string[]) => { if (value.length === 0) { @@ -209,10 +263,10 @@ describe("Options - conversions", () => { } }, }; - equal(convert(["1"], declaration, ""), ["1"]); - equal(convert(["1", "2"], declaration, ""), ["1", "2"]); + equal(convert(["1"], declaration, i18n, ""), ["1"]); + equal(convert(["1", "2"], declaration, i18n, ""), ["1", "2"]); throws( - () => convert([], declaration, ""), + () => convert([], declaration, i18n, ""), new Error("test must not be empty"), ); }); @@ -220,7 +274,7 @@ describe("Options - conversions", () => { it("Converts to mapped types", () => { const declaration: MapDeclarationOption<number> = { name: "", - help: "", + help: emptyHelp, type: ParameterType.Map, map: { a: 1, @@ -228,15 +282,15 @@ describe("Options - conversions", () => { }, defaultValue: 1, }; - equal(convert("a", declaration, ""), 1); - equal(convert("b", declaration, ""), 2); - equal(convert(2, declaration, ""), 2); + equal(convert("a", declaration, i18n, ""), 1); + equal(convert("b", declaration, i18n, ""), 2); + equal(convert(2, declaration, i18n, ""), 2); }); it("Converts to mapped types with a map", () => { const declaration: MapDeclarationOption<number> = { name: "", - help: "", + help: emptyHelp, type: ParameterType.Map, map: new Map([ ["a", 1], @@ -244,30 +298,15 @@ describe("Options - conversions", () => { ]), defaultValue: 1, }; - equal(convert("a", declaration, ""), 1); - equal(convert("b", declaration, ""), 2); - equal(convert(2, declaration, ""), 2); - }); - - it("Uses the mapError if provided for errors", () => { - const declaration: MapDeclarationOption<number> = { - name: "", - help: "", - type: ParameterType.Map, - map: {}, - defaultValue: 1, - mapError: "Test error", - }; - throws( - () => convert("a", declaration, ""), - new Error(declaration.mapError), - ); + equal(convert("a", declaration, i18n, ""), 1); + equal(convert("b", declaration, i18n, ""), 2); + equal(convert(2, declaration, i18n, ""), 2); }); - it("Generates a nice error if no mapError is provided", () => { + it("Generates a nice error if value is invalid", () => { const declaration: MapDeclarationOption<number> = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Map, map: new Map([ ["a", 1], @@ -276,7 +315,7 @@ describe("Options - conversions", () => { defaultValue: 1, }; throws( - () => convert("c", declaration, ""), + () => convert("c", declaration, i18n, ""), new Error("test must be one of a, b"), ); }); @@ -288,26 +327,29 @@ describe("Options - conversions", () => { } const declaration = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Map, map: Enum, defaultValue: Enum.a, } as const; throws( - () => convert("c", declaration, ""), + () => convert("c", declaration, i18n, ""), new Error("test must be one of a, b"), ); }); it("Passes through mixed", () => { const data = Symbol(); - equal(convert(data, optionWithType(ParameterType.Mixed), ""), data); + equal( + convert(data, optionWithType(ParameterType.Mixed), i18n, ""), + data, + ); }); it("Validates mixed options", () => { const declaration: MixedDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Mixed, defaultValue: "default", validate: (value: unknown) => { @@ -316,21 +358,24 @@ describe("Options - conversions", () => { } }, }; - equal(convert("text", declaration, ""), "text"); + equal(convert("text", declaration, i18n, ""), "text"); throws( - () => convert(1, declaration, ""), + () => convert(1, declaration, i18n, ""), new Error("test must not be a number"), ); }); it("Passes through object", () => { const data = {}; - equal(convert(data, optionWithType(ParameterType.Object), ""), data); + equal( + convert(data, optionWithType(ParameterType.Object), i18n, ""), + data, + ); }); it("Validates object options", () => { const declaration: ObjectDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Object, defaultValue: "default", validate: (value: unknown) => { @@ -339,9 +384,9 @@ describe("Options - conversions", () => { } }, }; - equal(convert({}, declaration, ""), {}); + equal(convert({}, declaration, i18n, ""), {}); throws( - () => convert(1, declaration, ""), + () => convert(1, declaration, i18n, ""), new Error("test must be an object"), ); }); @@ -349,14 +394,17 @@ describe("Options - conversions", () => { it("Converts object options", () => { const declaration: ObjectDeclarationOption = { name: "test", - help: "", + help: emptyHelp, type: ParameterType.Object, defaultValue: { a: 1, b: 2 }, }; - equal(convert({ b: 3 }, declaration, "", declaration.defaultValue), { - a: 1, - b: 3, - }); + equal( + convert({ b: 3 }, declaration, i18n, "", declaration.defaultValue), + { + a: 1, + b: 3, + }, + ); }); }); @@ -369,7 +417,7 @@ describe("Options - default values", () => { type, defaultValue, name: "test", - help: "", + help: emptyHelp, } as DeclarationOption; } diff --git a/src/test/utils/options/default-options.test.ts b/src/test/utils/options/default-options.test.ts index fc1ab95cf..a7c7900b0 100644 --- a/src/test/utils/options/default-options.test.ts +++ b/src/test/utils/options/default-options.test.ts @@ -1,26 +1,23 @@ import { ok, throws, strictEqual, doesNotThrow } from "assert"; -import { BUNDLED_THEMES } from "shiki"; import { Options } from "../../../lib/utils"; +import { Internationalization } from "../../../lib/internationalization/internationalization"; describe("Default Options", () => { - const opts = new Options(); + const opts = new Options(new Internationalization(null).proxy); describe("Highlighting theme", () => { it("Errors if an invalid theme is provided", () => { throws(() => opts.setValue("lightHighlightTheme", "randomTheme" as never), ); - opts.setValue("lightHighlightTheme", BUNDLED_THEMES[0]); - strictEqual( - opts.getValue("lightHighlightTheme"), - BUNDLED_THEMES[0], - ); + opts.setValue("lightHighlightTheme", "github-light"); + strictEqual(opts.getValue("lightHighlightTheme"), "github-light"); throws(() => opts.setValue("darkHighlightTheme", "randomTheme" as never), ); - opts.setValue("darkHighlightTheme", BUNDLED_THEMES[0]); - strictEqual(opts.getValue("darkHighlightTheme"), BUNDLED_THEMES[0]); + opts.setValue("darkHighlightTheme", "github-light"); + strictEqual(opts.getValue("darkHighlightTheme"), "github-light"); }); }); @@ -46,19 +43,19 @@ describe("Default Options", () => { }); }); - describe("markedOptions", () => { + describe("markdownItOptions", () => { it("Errors if given a non-object", () => { - throws(() => opts.setValue("markedOptions", null)); - throws(() => opts.setValue("markedOptions", "bad")); - throws(() => opts.setValue("markedOptions", [])); + throws(() => opts.setValue("markdownItOptions", null as never)); + throws(() => opts.setValue("markdownItOptions", "bad" as never)); + throws(() => opts.setValue("markdownItOptions", [] as never)); }); }); describe("compilerOptions", () => { it("Errors if given a non-object", () => { - throws(() => opts.setValue("markedOptions", null)); - throws(() => opts.setValue("markedOptions", "bad")); - throws(() => opts.setValue("markedOptions", [])); + throws(() => opts.setValue("compilerOptions", "bad")); + throws(() => opts.setValue("compilerOptions", null)); + throws(() => opts.setValue("compilerOptions", [])); }); }); diff --git a/src/test/utils/options/help.test.ts b/src/test/utils/options/help.test.ts index 646d01f9a..b57e2bb2a 100644 --- a/src/test/utils/options/help.test.ts +++ b/src/test/utils/options/help.test.ts @@ -2,9 +2,11 @@ import { ok } from "assert"; import { Options, ParameterType, ParameterHint } from "../../../lib/utils"; import { getOptionsHelp } from "../../../lib/utils/options/help"; +import { Internationalization } from "../../../lib/internationalization/internationalization"; describe("Options - help", () => { - const options = new Options(); + const i18n = new Internationalization(null).proxy; + const options = new Options(new Internationalization(null).proxy); for (const decl of [ { name: "td-option", help: "help", type: ParameterType.String }, { name: "td-option2", help: "help" }, @@ -19,12 +21,12 @@ describe("Options - help", () => { } it("Describes TypeDoc options", () => { - const help = getOptionsHelp(options); + const help = getOptionsHelp(options, i18n); ok(help.includes("td-option")); }); it("Does not list options without help", () => { - const help = getOptionsHelp(options); + const help = getOptionsHelp(options, i18n); ok(!help.includes("not displayed")); }); }); diff --git a/src/test/utils/options/options.test.ts b/src/test/utils/options/options.test.ts index 5b4f17afd..2d4a366a0 100644 --- a/src/test/utils/options/options.test.ts +++ b/src/test/utils/options/options.test.ts @@ -1,14 +1,15 @@ import { LogLevel, Options, ParameterType } from "../../../lib/utils"; import { Option, - MapDeclarationOption, - NumberDeclarationOption, + type MapDeclarationOption, + type NumberDeclarationOption, } from "../../../lib/utils"; import { deepStrictEqual as equal, throws } from "assert"; import type { DeclarationOption, EmitStrategy, } from "../../../lib/utils/options"; +import { Internationalization } from "../../../lib/internationalization/internationalization"; describe("Options", () => { let options: Options & { @@ -17,13 +18,13 @@ describe("Options", () => { }; beforeEach(() => { - options = new Options(); + options = new Options(new Internationalization(null).proxy); options.addDeclaration({ name: "mapped", type: ParameterType.Map, map: { a: 1 }, defaultValue: 2, - help: "", + help: () => "", }); }); @@ -32,7 +33,7 @@ describe("Options", () => { try { options.addDeclaration({ name: "help", - help: "", + help: () => "", type: ParameterType.Boolean, }); } catch { @@ -45,7 +46,7 @@ describe("Options", () => { it("Does not throw if number declaration has no min and max values", () => { const declaration: NumberDeclarationOption = { name: "test-number-declaration", - help: "", + help: () => "", type: ParameterType.Number, defaultValue: 1, }; @@ -55,7 +56,7 @@ describe("Options", () => { it("Does not throw if default value is out of range for number declaration", () => { const declaration: NumberDeclarationOption = { name: "test-number-declaration", - help: "", + help: () => "", type: ParameterType.Number, minValue: 1, maxValue: 10, @@ -67,7 +68,7 @@ describe("Options", () => { it("Does not throw if a map declaration has a default value that is not part of the map of possible values", () => { const declaration: MapDeclarationOption<number> = { name: "testMapDeclarationWithForeignDefaultValue", - help: "", + help: () => "", type: ParameterType.Map, map: new Map([ ["a", 1], @@ -121,7 +122,7 @@ describe("Options", () => { }); it("Resets a flag to the default if set to null", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("validation", { notExported: true }); options.setValue("validation", { notExported: null! }); @@ -133,7 +134,7 @@ describe("Options", () => { }); it("Handles mapped enums properly", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); equal(options.getValue("logLevel"), LogLevel.Info); options.setValue("logLevel", LogLevel.Error); @@ -147,7 +148,7 @@ describe("Options", () => { }); it("Supports checking if an option is set", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); equal(options.isSet("excludePrivate"), false); options.setValue("excludePrivate", false); equal(options.isSet("excludePrivate"), true); @@ -158,7 +159,7 @@ describe("Options", () => { }); it("Throws if frozen and a value is set", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.freeze(); throws(() => options.setValue("categorizeByGroup", true)); @@ -166,7 +167,7 @@ describe("Options", () => { }); it("Supports resetting values", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("entryPoints", ["x"]); const oldExcludeTags = options.getValue("excludeTags"); @@ -178,7 +179,7 @@ describe("Options", () => { }); it("Supports resetting a single value", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("name", "test"); const originalExclude = options.getValue("excludeTags"); @@ -190,7 +191,7 @@ describe("Options", () => { }); it("Throws if resetting a single value which does not exist", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); throws(() => options.reset("thisOptionDoesNotExist" as never)); }); @@ -205,14 +206,14 @@ describe("Option", () => { } it("Supports fetching options", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); const container = new Container(options); equal(container.emit, "docs"); }); it("Updates as option values change", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); const container = new Container(options); equal(container.emit, "docs"); diff --git a/src/test/utils/options/readers/arguments.test.ts b/src/test/utils/options/readers/arguments.test.ts index 25efd00c6..12bacb504 100644 --- a/src/test/utils/options/readers/arguments.test.ts +++ b/src/test/utils/options/readers/arguments.test.ts @@ -1,14 +1,17 @@ -import { deepStrictEqual as equal, ok } from "assert"; +import { deepStrictEqual as equal } from "assert"; -import { Options, Logger } from "../../../../lib/utils"; +import { Options } from "../../../../lib/utils"; import { ArgumentsReader } from "../../../../lib/utils/options/readers"; import { ParameterType, - NumberDeclarationOption, - MapDeclarationOption, + type NumberDeclarationOption, + type MapDeclarationOption, } from "../../../../lib/utils/options"; import { join, resolve } from "path"; import { TestLogger } from "../../../TestLogger"; +import { Internationalization } from "../../../../lib/internationalization/internationalization"; + +const emptyHelp = () => ""; describe("Options - ArgumentsReader", () => { const logger = new TestLogger(); @@ -32,16 +35,16 @@ describe("Options - ArgumentsReader", () => { }; beforeEach(() => { - options = new Options(); + options = new Options(new Internationalization(null).proxy); options.addDeclaration({ name: "numOption", - help: "", + help: emptyHelp, type: ParameterType.Number, }); options.addDeclaration({ name: "mapped", type: ParameterType.Map, - help: "", + help: emptyHelp, map: { a: 1, b: 2 }, defaultValue: 3, }); @@ -135,19 +138,15 @@ describe("Options - ArgumentsReader", () => { }); it("Warns if option is expecting a value but no value is provided", async () => { - let check = false; - class TestLogger extends Logger { - override warn(msg: string) { - ok(msg.includes("--out")); - check = true; - } - } - const reader = new ArgumentsReader(1, ["--out"]); options.reset(); options.addReader(reader); - await options.read(new TestLogger()); - equal(check, true, "Reader did not report an error."); + const logger = new TestLogger(); + await options.read(logger); + logger.expectMessage( + "warn: --out expected a value, but none was given as an argument", + ); + logger.expectNoOtherMessages(); }); test( diff --git a/src/test/utils/options/readers/package-json.test.ts b/src/test/utils/options/readers/package-json.test.ts index a40ff95d4..fbeed7122 100644 --- a/src/test/utils/options/readers/package-json.test.ts +++ b/src/test/utils/options/readers/package-json.test.ts @@ -3,13 +3,14 @@ import { project } from "@typestrong/fs-fixture-builder"; import { PackageJsonReader } from "../../../../lib/utils/options/readers"; import { Options } from "../../../../lib/utils"; import { TestLogger } from "../../../TestLogger"; +import { Internationalization } from "../../../../lib/internationalization/internationalization"; describe("Options - PackageJsonReader", () => { let optsContainer: Options; let testLogger: TestLogger; beforeEach(() => { - optsContainer = new Options(); + optsContainer = new Options(new Internationalization(null).proxy); testLogger = new TestLogger(); optsContainer.addReader(new PackageJsonReader()); @@ -29,11 +30,10 @@ describe("Options - PackageJsonReader", () => { const proj = project(testTitle.replace(/[ "]/g, "_")); proj.addFile("package.json", pkgJsonContent); proj.write(); + after(() => proj.rm()); await optsContainer.read(testLogger, proj.cwd); - proj.rm(); - test(testLogger); testLogger.expectNoOtherMessages(); }); @@ -50,7 +50,7 @@ describe("Options - PackageJsonReader", () => { `{ "name": "x", "typedocOptions": 123 }`, (l) => l.expectMessage( - `error: Failed to parse the "typedocOptions" field in */package.json, ensure it exists and contains an object.`, + `error: Failed to parse the "typedocOptions" field in */package.json, ensure it exists and contains an object`, ), ); @@ -59,7 +59,7 @@ describe("Options - PackageJsonReader", () => { `{ "name": "x", "typedocOptions": { "someOptionThatDoesNotExist": true } }`, (l) => l.expectMessage( - "error: Tried to set an option (someOptionThatDoesNotExist) that was not declared.*", + "error: Unknown option 'someOptionThatDoesNotExist' You may have meant:*", ), ); @@ -68,7 +68,7 @@ describe("Options - PackageJsonReader", () => { `{ "name": "x", "typedoc": {} }`, (l) => l.expectMessage( - "warn: The 'typedoc' key in */package.json was used by the legacy-packages entryPointStrategy and will be ignored.", + "warn: The 'typedoc' key in */package.json was used by the legacy-packages entryPointStrategy and will be ignored", ), ); }); diff --git a/src/test/utils/options/readers/tsconfig.test.ts b/src/test/utils/options/readers/tsconfig.test.ts index b4be6e0c2..9bf5cf80b 100644 --- a/src/test/utils/options/readers/tsconfig.test.ts +++ b/src/test/utils/options/readers/tsconfig.test.ts @@ -4,11 +4,12 @@ import { deepStrictEqual as equal } from "assert"; import { TSConfigReader } from "../../../../lib/utils/options/readers"; import { Logger, Options } from "../../../../lib/utils"; import { TestLogger } from "../../../TestLogger"; -import { tempdirProject, Project } from "@typestrong/fs-fixture-builder"; +import { tempdirProject, type Project } from "@typestrong/fs-fixture-builder"; import { tmpdir } from "os"; +import { Internationalization } from "../../../../lib/internationalization/internationalization"; describe("Options - TSConfigReader", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.addReader(new TSConfigReader()); const logger = new TestLogger(); @@ -28,7 +29,6 @@ describe("Options - TSConfigReader", () => { if (noErrors) { logger.expectNoOtherMessages(); } - project.rm(); } it("Errors if the file cannot be found", async () => { @@ -45,7 +45,7 @@ describe("Options - TSConfigReader", () => { function testError(name: string, file: object) { it(name, async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addJsonFile("tsconfig.json", file); await readWithProject(project, true, false); equal(logger.hasErrors(), true, "No error was logged"); @@ -75,7 +75,7 @@ describe("Options - TSConfigReader", () => { }); it("Errors if a tsconfig file cannot be parsed", async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addFile("tsconfig.json", '{"test}'); await readWithProject(project, true, false); logger.expectMessage("error: *"); @@ -88,7 +88,7 @@ describe("Options - TSConfigReader", () => { override isSet() { return false; } - })(); + })(new Internationalization(null).proxy); options.setValue( "tsconfig", @@ -100,7 +100,7 @@ describe("Options - TSConfigReader", () => { }); it("Reads typedocOptions from extended tsconfig files", async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addFile("file.ts", "export const abc = 123"); project.addJsonFile("tsconfig.json", { extends: ["./base.tsconfig.json"], @@ -118,7 +118,7 @@ describe("Options - TSConfigReader", () => { }); async function readTsconfig(tsconfig: object) { - const project = tempdirProject(); + using project = tempdirProject(); project.addFile("file.ts", "export const abc = 123"); project.addJsonFile("tsconfig.json", tsconfig); @@ -146,7 +146,7 @@ describe("Options - TSConfigReader", () => { }); it("Does not set excludeInternal by stripInternal if already set", async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addJsonFile("tsconfig.json", { compilerOptions: { stripInternal: true }, }); @@ -158,7 +158,7 @@ describe("Options - TSConfigReader", () => { }); it("Correctly handles folder names ending with .json (#1712)", async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addJsonFile("tsconfig.json", { compilerOptions: { strict: true }, }); @@ -167,7 +167,7 @@ describe("Options - TSConfigReader", () => { }); async function testTsdoc(tsdoc: object, cb?: () => void, reset = true) { - const project = tempdirProject(); + using project = tempdirProject(); project.addFile("file.ts", "export const abc = 123"); project.addJsonFile("tsconfig.json", {}); project.addJsonFile("tsdoc.json", tsdoc); @@ -180,7 +180,7 @@ describe("Options - TSConfigReader", () => { it("Handles failed tsdoc reads", async () => { await testTsdoc([], () => { logger.expectMessage( - "error: Failed to read tsdoc.json file at */tsdoc.json.", + "error: Failed to read tsdoc.json file at */tsdoc.json", ); }); }); @@ -192,7 +192,7 @@ describe("Options - TSConfigReader", () => { }, () => { logger.expectMessage( - `error: The file */tsdoc.json is not a valid tsdoc.json file.`, + `error: The file */tsdoc.json is not a valid tsdoc.json file`, ); }, ); @@ -207,7 +207,7 @@ describe("Options - TSConfigReader", () => { () => { logger.expectMessage( "warn: The blockTags, modifierTags defined in typedoc.json " + - "will be overwritten by configuration in tsdoc.json.", + "will be overwritten by configuration in tsdoc.json", ); }, false, @@ -239,7 +239,7 @@ describe("Options - TSConfigReader", () => { }); it("Handles extends in tsdoc.json", async () => { - const project = tempdirProject(); + using project = tempdirProject(); project.addFile("file.ts", "export const abc = 123"); project.addJsonFile("tsconfig.json", {}); project.addJsonFile("tsdoc.json", { extends: ["./tsdoc2.json"] }); diff --git a/src/test/utils/options/readers/typedoc.test.ts b/src/test/utils/options/readers/typedoc.test.ts index 930c44382..c1646de9c 100644 --- a/src/test/utils/options/readers/typedoc.test.ts +++ b/src/test/utils/options/readers/typedoc.test.ts @@ -5,13 +5,16 @@ import { TypeDocReader } from "../../../../lib/utils/options/readers"; import { Logger, Options } from "../../../../lib/utils"; import { TestLogger } from "../../../TestLogger"; import { join } from "path"; +import { Internationalization } from "../../../../lib/internationalization/internationalization"; describe("Options - TypeDocReader", () => { - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.addReader(new TypeDocReader()); it("Supports comments in json", async () => { const project = fsProject("jsonc"); + after(() => project.rm()); + project.addFile("typedoc.json", '//comment\n{"name": "comment"}'); const logger = new TestLogger(); @@ -19,7 +22,6 @@ describe("Options - TypeDocReader", () => { options.reset(); options.setValue("options", project.cwd); await options.read(logger); - project.rm(); logger.expectNoOtherMessages(); equal(options.getValue("name"), "comment"); @@ -37,10 +39,10 @@ describe("Options - TypeDocReader", () => { const logger = new TestLogger(); project.write(); + after(() => project.rm()); options.reset(); options.setValue("options", project.cwd); await options.read(logger); - project.rm(); logger.expectNoOtherMessages(); equal(options.getValue("name"), "extends"); @@ -53,10 +55,10 @@ describe("Options - TypeDocReader", () => { const logger = new TestLogger(); project.write(); + after(() => project.rm()); options.reset(); options.setValue("options", project.cwd); await options.read(logger); - project.rm(); logger.expectNoOtherMessages(); equal(options.getValue("name"), "js"); @@ -68,7 +70,7 @@ describe("Options - TypeDocReader", () => { const logger = new TestLogger(); await options.read(logger); logger.expectMessage( - "error: The options file */non-existent-file.json does not exist.", + "error: The options file */non-existent-file.json does not exist", ); logger.expectNoOtherMessages(); }); @@ -92,8 +94,8 @@ describe("Options - TypeDocReader", () => { options.setValue("options", project.cwd); const logger = new TestLogger(); project.write(); + after(() => project.rm()); await options.read(logger); - project.rm(); logger.expectMessage(message); }); } @@ -101,17 +103,17 @@ describe("Options - TypeDocReader", () => { testError( "Errors if the data is invalid", "Not valid json {}", - "error: Failed to parse */typedoc.json, ensure it exists and contains an object.", + "error: Failed to parse */typedoc.json, ensure it exists and exports an object", ); testError( "Errors if the data is not an object in a json file", 123, - "error: Failed to parse */typedoc.json, ensure it exists and contains an object.", + "error: Failed to parse */typedoc.json, ensure it exists and exports an object", ); testError( "Errors if the data is not an object in a js file", "module.exports = 123", - "error: The root value of */typedoc.js is not an object.", + "error: Failed to parse */typedoc.js, ensure it exists and exports an object", false, ); testError( @@ -119,14 +121,14 @@ describe("Options - TypeDocReader", () => { { someOptionThatDoesNotExist: true, }, - "error: Tried to set an option (someOptionThatDoesNotExist) that was not declared. You may have meant:*", + "error: Unknown option 'someOptionThatDoesNotExist' You may have meant:*", ); testError( "Errors if extends results in a loop", { extends: "./typedoc.json", }, - "error: Tried to load the options file */typedoc.json multiple times.", + 'error: Circular reference encountered for "extends" field of *', ); testError( "Errors if the extended path cannot be found", @@ -141,7 +143,7 @@ describe("Options - TypeDocReader", () => { override isSet() { return false; } - })(); + })(new Internationalization(null).proxy); options.addReader(new TypeDocReader()); const logger = new Logger(); @@ -156,44 +158,49 @@ describe("Options - TypeDocReader", () => { "export default { pretty: false }", ); project.write(); + after(() => project.rm()); const logger = new TestLogger(); - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("options", join(project.cwd, "typedoc.config.mjs")); options.addReader(new TypeDocReader()); await options.read(logger); equal(logger.hasErrors(), false); - - project.rm(); }); it("Handles errors when reading config files", async () => { const project = fsProject("errors"); project.addFile("typedoc.config.mjs", "throw new Error('hi')"); project.write(); + after(() => project.rm()); const logger = new TestLogger(); - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("options", join(project.cwd, "typedoc.config.mjs")); options.addReader(new TypeDocReader()); await options.read(logger); - project.rm(); - logger.expectMessage("error: Failed to read */typedoc.config.mjs: hi"); + logger.expectMessage( + "error: Failed to parse */typedoc.config.mjs, ensure it exists and exports an object", + ); + logger.expectMessage("error: hi"); }); it("Handles non-Error throws when reading config files", async () => { const project = fsProject("errors2"); project.addFile("typedoc.config.cjs", "throw 123"); project.write(); + after(() => project.rm()); const logger = new TestLogger(); - const options = new Options(); + const options = new Options(new Internationalization(null).proxy); options.setValue("options", join(project.cwd, "typedoc.config.cjs")); options.addReader(new TypeDocReader()); await options.read(logger); - project.rm(); - logger.expectMessage("error: Failed to read */typedoc.config.cjs: 123"); + logger.expectMessage( + "error: Failed to parse */typedoc.config.cjs, ensure it exists and exports an object", + ); + logger.expectMessage("error: 123"); }); }); diff --git a/src/test/utils/options/tsdoc-defaults.test.ts b/src/test/utils/options/tsdoc-defaults.test.ts new file mode 100644 index 000000000..7d636a0bb --- /dev/null +++ b/src/test/utils/options/tsdoc-defaults.test.ts @@ -0,0 +1,61 @@ +import { deepEqual as equal } from "assert/strict"; +import { join } from "path"; +import ts from "typescript"; +import * as defaults from "../../../lib/utils/options/tsdoc-defaults"; + +describe("tsdoc-defaults.ts", () => { + const tsdoc = ts.readConfigFile( + join(__dirname, "../../../../tsdoc.json"), + ts.sys.readFile, + ); + const tagDefinitions = tsdoc.config?.tagDefinitions as Array<{ + tagName: string; + syntaxKind: "block" | "modifier" | "inline"; + }>; + + function tagsByKind(kind: "block" | "modifier" | "inline") { + return tagDefinitions + .filter((t) => t.syntaxKind === kind) + .map((t) => t.tagName) + .sort((a, b) => a.localeCompare(b)); + } + + before(() => { + equal(tsdoc.error, undefined); + }); + + it("Should expose the same block tags as the tsdoc.json file", () => { + const tsdocTags = tagsByKind("block"); + + const typedocTags = defaults.blockTags + .filter((t) => !defaults.tsdocBlockTags.includes(t as never)) + .sort((a, b) => a.localeCompare(b)); + + // @inheritDoc is a special case. We can't specify it in the tsdoc.json + // or the official parser blows up, because it thinks that it is only + // an inline tag. + typedocTags.splice(typedocTags.indexOf("@inheritDoc"), 1); + + equal(tsdocTags, typedocTags); + }); + + it("Should expose the same modifier tags as the tsdoc.json file", () => { + const tsdocTags = tagsByKind("modifier"); + + const typedocTags = defaults.modifierTags + .filter((t) => !defaults.tsdocModifierTags.includes(t as never)) + .sort((a, b) => a.localeCompare(b)); + + equal(tsdocTags, typedocTags); + }); + + it("Should expose the same inline tags as the tsdoc.json file", () => { + const tsdocTags = tagsByKind("inline"); + + const typedocTags = defaults.inlineTags + .filter((t) => !defaults.tsdocInlineTags.includes(t as never)) + .sort((a, b) => a.localeCompare(b)); + + equal(tsdocTags, typedocTags); + }); +}); diff --git a/src/test/utils/path.test.ts b/src/test/utils/path.test.ts new file mode 100644 index 000000000..70c84df3d --- /dev/null +++ b/src/test/utils/path.test.ts @@ -0,0 +1,27 @@ +import { equal } from "assert"; +import { normalizePath } from "../../lib/utils"; + +describe("normalizePath", () => { + const winTest = process.platform === "win32" ? it : it.skip; + const nixTest = process.platform === "win32" ? it.skip : it; + + winTest("Returns paths with forward slashes", () => { + equal( + normalizePath("test\\test\\another/forward"), + "test/test/another/forward", + ); + }); + + winTest("Normalizes drive letters", () => { + equal(normalizePath("c:\\foo"), "C:/foo"); + equal(normalizePath("D:/foo"), "D:/foo"); + }); + + winTest("Checks for unix style paths", () => { + equal(normalizePath("/c/users/you"), "C:/users/you"); + }); + + nixTest("Returns the original path", () => { + equal(normalizePath("/c/users\\foo"), "/c/users\\foo"); + }); +}); diff --git a/src/test/utils/plugins.test.ts b/src/test/utils/plugins.test.ts index 113a1655e..17705534c 100644 --- a/src/test/utils/plugins.test.ts +++ b/src/test/utils/plugins.test.ts @@ -1,23 +1,21 @@ -import { Project, tempdirProject } from "@typestrong/fs-fixture-builder"; +import { tempdirProject } from "@typestrong/fs-fixture-builder"; import type { Application } from "../../index"; import { loadPlugins } from "../../lib/utils/plugins"; import { TestLogger } from "../TestLogger"; import { join, resolve } from "path"; +import { Internationalization } from "../../lib/internationalization/internationalization"; describe("loadPlugins", () => { - let project: Project; let logger: TestLogger; - const fakeApp = {} as any as Application; + const fakeApp = { + i18n: new Internationalization(null).proxy, + } as any as Application; beforeEach(() => { - project = tempdirProject(); logger = fakeApp.logger = new TestLogger(); }); - afterEach(() => { - project.rm(); - }); - it("Should support loading a basic plugin", async () => { + using project = tempdirProject(); project.addJsonFile("package.json", { type: "commonjs", main: "index.js", @@ -31,6 +29,7 @@ describe("loadPlugins", () => { }); it("Should support loading a ESM plugin", async () => { + using project = tempdirProject(); project.addJsonFile("package.json", { type: "module", main: "index.js", @@ -44,6 +43,7 @@ describe("loadPlugins", () => { }); it("Should handle errors when requiring plugins", async () => { + using project = tempdirProject(); project.addJsonFile("package.json", { type: "commonjs", main: "index.js", @@ -53,12 +53,11 @@ describe("loadPlugins", () => { const plugin = join(resolve(project.cwd), "index.js"); await loadPlugins(fakeApp, [plugin]); - logger.expectMessage( - `error: The plugin ${plugin} could not be loaded.`, - ); + logger.expectMessage(`error: The plugin ${plugin} could not be loaded`); }); it("Should handle errors when loading plugins", async () => { + using project = tempdirProject(); project.addJsonFile("package.json", { type: "commonjs", main: "index.js", @@ -71,12 +70,11 @@ describe("loadPlugins", () => { const plugin = join(resolve(project.cwd), "index.js"); await loadPlugins(fakeApp, [plugin]); - logger.expectMessage( - `error: The plugin ${plugin} could not be loaded.`, - ); + logger.expectMessage(`error: The plugin ${plugin} could not be loaded`); }); it("Should handle plugins without a load method", async () => { + using project = tempdirProject(); project.addJsonFile("package.json", { type: "commonjs", main: "index.js", @@ -87,7 +85,7 @@ describe("loadPlugins", () => { const plugin = join(resolve(project.cwd), "index.js"); await loadPlugins(fakeApp, [plugin]); logger.expectMessage( - `error: Invalid structure in plugin ${plugin}, no load function found.`, + `error: Invalid structure in plugin ${plugin}, no load function found`, ); }); }); diff --git a/src/test/utils/sort.test.ts b/src/test/utils/sort.test.ts index 2c1fd49b8..c297b35e8 100644 --- a/src/test/utils/sort.test.ts +++ b/src/test/utils/sort.test.ts @@ -1,6 +1,7 @@ import { deepStrictEqual as equal } from "assert"; import { DeclarationReflection, + DocumentReflection, LiteralType, ProjectReflection, ReflectionFlag, @@ -9,14 +10,16 @@ import { } from "../../lib/models"; import { resetReflectionID } from "../../lib/models/reflections/abstract"; import { Options } from "../../lib/utils"; -import { getSortFunction, SortStrategy } from "../../lib/utils/sort"; +import { getSortFunction, type SortStrategy } from "../../lib/utils/sort"; +import { Internationalization } from "../../lib/internationalization/internationalization"; +import { FileRegistry } from "../../lib/models/FileRegistry"; describe("Sort", () => { function sortReflections( - arr: DeclarationReflection[], + arr: Array<DeclarationReflection | DocumentReflection>, strategies: SortStrategy[], ) { - const opts = new Options(); + const opts = new Options(new Internationalization(null).proxy); opts.setValue("sort", strategies); getSortFunction(opts)(arr); } @@ -245,7 +248,7 @@ describe("Sort", () => { }); it("source-order should do nothing if no symbols are available", () => { - const proj = new ProjectReflection(""); + const proj = new ProjectReflection("", new FileRegistry()); const arr = [ new DeclarationReflection("b", ReflectionKind.Function, proj), new DeclarationReflection("a", ReflectionKind.Function, proj), @@ -275,7 +278,7 @@ describe("Sort", () => { }); cId.pos = 0; - const proj = new ProjectReflection(""); + const proj = new ProjectReflection("", new FileRegistry()); const a = new DeclarationReflection("a", ReflectionKind.Variable, proj); proj.registerSymbolId(a, aId); @@ -306,7 +309,7 @@ describe("Sort", () => { }); cId.pos = 1; - const proj = new ProjectReflection(""); + const proj = new ProjectReflection("", new FileRegistry()); const a = new DeclarationReflection("a", ReflectionKind.Variable, proj); const b = new DeclarationReflection( @@ -332,4 +335,46 @@ describe("Sort", () => { ["a", "c", "b", "d"], ); }); + + it("Should handle documents-first ordering", () => { + const proj = new ProjectReflection("", new FileRegistry()); + const a = new DocumentReflection("a", proj, [], {}); + const b = new DocumentReflection("b", proj, [], {}); + const c = new DeclarationReflection("c", ReflectionKind.Class, proj); + + const arr = [a, b, c]; + sortReflections(arr, ["documents-first", "alphabetical"]); + equal( + arr.map((r) => r.name), + ["a", "b", "c"], + ); + + const arr2 = [c, b, a]; + sortReflections(arr2, ["documents-first", "alphabetical"]); + equal( + arr2.map((r) => r.name), + ["a", "b", "c"], + ); + }); + + it("Should handle documents-last ordering", () => { + const proj = new ProjectReflection("", new FileRegistry()); + const a = new DocumentReflection("a", proj, [], {}); + const b = new DocumentReflection("b", proj, [], {}); + const c = new DeclarationReflection("c", ReflectionKind.Class, proj); + + const arr = [a, b, c]; + sortReflections(arr, ["documents-last", "alphabetical"]); + equal( + arr.map((r) => r.name), + ["c", "a", "b"], + ); + + const arr2 = [a, c, b]; + sortReflections(arr2, ["documents-last", "alphabetical"]); + equal( + arr2.map((r) => r.name), + ["c", "a", "b"], + ); + }); }); diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index 549b1340e..5b311eb72 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -1,6 +1,5 @@ import { ok } from "assert"; import { join } from "path"; -import { Logger, LogLevel } from ".."; import { validateDocumentation } from "../lib/validation/documentation"; import { validateExports } from "../lib/validation/exports"; import { getConverter2App, getConverter2Program } from "./programs"; @@ -38,7 +37,7 @@ function expectWarning( validateExports(project, logger, intentionallyNotExported); logger.expectMessage( - `warn: ${typeName}, defined in */${file}, is referenced by ${referencingName} but not included in the documentation.`, + `warn: ${typeName}, defined in */${file}, is referenced by ${referencingName} but not included in the documentation`, ); } @@ -86,11 +85,11 @@ describe("validateExports", () => { }); it("Should warn if a get signature type is missing", () => { - expectWarning("Bar", "getSignature.ts", "Foo.foo.foo"); + expectWarning("Bar", "getSignature.ts", "Foo.foo"); }); it("Should warn if a set signature type is missing", () => { - expectWarning("Bar", "setSignature.ts", "Foo.foo.foo._value"); + expectWarning("Bar", "setSignature.ts", "Foo.foo._value"); }); it("Should warn if an implemented type is missing", () => { @@ -98,11 +97,11 @@ describe("validateExports", () => { }); it("Should warn if a parameter type is missing", () => { - expectWarning("Bar", "parameter.ts", "Foo.Foo.x"); + expectWarning("Bar", "parameter.ts", "Foo.x"); }); it("Should warn if a return type is missing", () => { - expectWarning("Bar", "return.ts", "foo.foo"); + expectWarning("Bar", "return.ts", "foo"); }); it("Should allow filtering warnings by file name", () => { @@ -140,28 +139,12 @@ describe("validateExports", () => { }, ]); - let sawWarning = false; - class LoggerCheck extends Logger { - override log(message: string, level: LogLevel) { - if ( - level == LogLevel.Warn && - message.includes("intentionally not exported") - ) { - sawWarning = true; - ok( - message.includes("notDefined"), - "Should have included a warning about notDefined", - ); - ok( - !message.includes("Foo"), - "Should not include a warn about Foo", - ); - } - } - } - - validateExports(project, new LoggerCheck(), ["notDefined", "Foo"]); - ok(sawWarning, "Never saw warning."); + const logger = new TestLogger(); + validateExports(project, logger, ["notDefined", "Foo"]); + logger.expectMessage( + "warn: The following symbols were marked as intentionally not exported, but were either not referenced in the documentation, or were exported:\n\tnotDefined", + ); + logger.expectNoOtherMessages(); }); }); @@ -172,7 +155,7 @@ describe("validateDocumentation", () => { validateDocumentation(project, logger, ["Function"]); logger.expectMessage( - "warn: bar (CallSignature), defined in */function.ts, does not have any documentation.", + "warn: bar (CallSignature), defined in */function.ts, does not have any documentation", ); logger.expectNoOtherMessages(); }); @@ -183,7 +166,7 @@ describe("validateDocumentation", () => { validateDocumentation(project, logger, ["Accessor"]); logger.expectMessage( - "warn: Foo.foo (GetSignature), defined in */getSignature.ts, does not have any documentation.", + "warn: Foo.foo (GetSignature), defined in */getSignature.ts, does not have any documentation", ); logger.expectNoOtherMessages(); }); @@ -194,7 +177,7 @@ describe("validateDocumentation", () => { validateDocumentation(project, logger, ["Constructor"]); logger.expectMessage( - "warn: Foo.constructor (ConstructorSignature), defined in */class.ts, does not have any documentation.", + "warn: Foo.constructor (ConstructorSignature), defined in */class.ts, does not have any documentation", ); logger.expectNoOtherMessages(); }); @@ -205,7 +188,7 @@ describe("validateDocumentation", () => { validateDocumentation(project, logger, ["Method"]); logger.expectMessage( - "warn: Foo.method (CallSignature), defined in */interface.ts, does not have any documentation.", + "warn: Foo.method (CallSignature), defined in */interface.ts, does not have any documentation", ); logger.expectNoOtherMessages(); }); diff --git a/static/style.css b/static/style.css index 778b94927..9d619a641 100644 --- a/static/style.css +++ b/static/style.css @@ -10,6 +10,7 @@ --light-color-text: #222; --light-color-text-aside: #6e6e6e; --light-color-link: #1f70c2; + --light-color-focus-outline: #3584e4; --light-color-ts-keyword: #056bd6; --light-color-ts-project: #b111c9; @@ -35,6 +36,7 @@ --light-color-ts-set-signature: var(--light-color-ts-accessor); --light-color-ts-type-alias: #d51270; /* reference not included as links will be colored with the kind that it points to */ + --light-color-document: #000000; --light-external-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='10' height='10'><path fill-opacity='0' stroke='%23000' stroke-width='10' d='m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z'/></svg>"); --light-color-scheme: light; @@ -50,6 +52,7 @@ --dark-color-text: #f5f5f5; --dark-color-text-aside: #dddddd; --dark-color-link: #00aff4; + --dark-color-focus-outline: #4c97f2; --dark-color-ts-keyword: #3399ff; --dark-color-ts-project: #e358ff; @@ -75,6 +78,7 @@ --dark-color-ts-set-signature: var(--dark-color-ts-accessor); --dark-color-ts-type-alias: #ff6492; /* reference not included as links will be colored with the kind that it points to */ + --dark-color-document: #ffffff; --dark-external-icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='10' height='10'><path fill-opacity='0' stroke='%23fff' stroke-width='10' d='m43,35H5v60h60V57M45,5v10l10,10-30,30 20,20 30-30 10,10h10V5z'/></svg>"); --dark-color-scheme: dark; @@ -92,6 +96,7 @@ --color-text: var(--light-color-text); --color-text-aside: var(--light-color-text-aside); --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); --color-ts-keyword: var(--light-color-ts-keyword); --color-ts-module: var(--light-color-ts-module); @@ -116,6 +121,7 @@ --color-ts-get-signature: var(--light-color-ts-get-signature); --color-ts-set-signature: var(--light-color-ts-set-signature); --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); --external-icon: var(--light-external-icon); --color-scheme: var(--light-color-scheme); @@ -134,6 +140,7 @@ --color-text: var(--dark-color-text); --color-text-aside: var(--dark-color-text-aside); --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); --color-ts-keyword: var(--dark-color-ts-keyword); --color-ts-module: var(--dark-color-ts-module); @@ -158,6 +165,7 @@ --color-ts-get-signature: var(--dark-color-ts-get-signature); --color-ts-set-signature: var(--dark-color-ts-set-signature); --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); --external-icon: var(--dark-external-icon); --color-scheme: var(--dark-color-scheme); @@ -183,6 +191,7 @@ body { --color-text: var(--light-color-text); --color-text-aside: var(--light-color-text-aside); --color-link: var(--light-color-link); + --color-focus-outline: var(--light-color-focus-outline); --color-ts-keyword: var(--light-color-ts-keyword); --color-ts-module: var(--light-color-ts-module); @@ -207,6 +216,7 @@ body { --color-ts-get-signature: var(--light-color-ts-get-signature); --color-ts-set-signature: var(--light-color-ts-set-signature); --color-ts-type-alias: var(--light-color-ts-type-alias); + --color-document: var(--light-color-document); --external-icon: var(--light-external-icon); --color-scheme: var(--light-color-scheme); @@ -223,6 +233,7 @@ body { --color-text: var(--dark-color-text); --color-text-aside: var(--dark-color-text-aside); --color-link: var(--dark-color-link); + --color-focus-outline: var(--dark-color-focus-outline); --color-ts-keyword: var(--dark-color-ts-keyword); --color-ts-module: var(--dark-color-ts-module); @@ -247,11 +258,17 @@ body { --color-ts-get-signature: var(--dark-color-ts-get-signature); --color-ts-set-signature: var(--dark-color-ts-set-signature); --color-ts-type-alias: var(--dark-color-ts-type-alias); + --color-document: var(--dark-color-document); --external-icon: var(--dark-external-icon); --color-scheme: var(--dark-color-scheme); } +*:focus-visible, +.tsd-accordion-summary:focus-visible svg { + outline: 2px solid var(--color-focus-outline); +} + .always-visible, .always-visible .tsd-signatures { display: inherit !important; @@ -266,16 +283,6 @@ h6 { line-height: 1.2; } -h1 > a:not(.link), -h2 > a:not(.link), -h3 > a:not(.link), -h4 > a:not(.link), -h5 > a:not(.link), -h6 > a:not(.link) { - text-decoration: none; - color: var(--color-text); -} - h1 { font-size: 1.875rem; margin: 0.67rem 0; @@ -306,10 +313,6 @@ h6 { margin: 2.33rem 0; } -.uppercase { - text-transform: uppercase; -} - dl, menu, ol, @@ -333,7 +336,7 @@ footer { padding-bottom: 1rem; max-height: 3.5rem; } -.tsd-generator { +footer > p { margin: 0 1em; } @@ -421,6 +424,9 @@ a.external[target="_blank"] { background-repeat: no-repeat; padding-right: 13px; } +a.tsd-anchor-link { + color: var(--color-text); +} code, pre { @@ -580,13 +586,13 @@ dl.tsd-comment-tag-group p { } .tsd-filter-input { display: flex; - width: fit-content; width: -moz-fit-content; + width: fit-content; align-items: center; - user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; + user-select: none; cursor: pointer; } .tsd-filter-input input[type="checkbox"] { @@ -609,11 +615,8 @@ dl.tsd-comment-tag-group p { Don't remove unless you know what you're doing. */ opacity: 0.99; } -.tsd-filter-input input[type="checkbox"]:focus + svg { - transform: scale(0.95); -} -.tsd-filter-input input[type="checkbox"]:focus:not(:focus-visible) + svg { - transform: scale(1); +.tsd-filter-input input[type="checkbox"]:focus-visible + svg { + outline: 2px solid var(--color-focus-outline); } .tsd-checkbox-background { fill: var(--color-accent); @@ -630,13 +633,18 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { stroke: var(--color-accent); } -.tsd-theme-toggle { - padding-top: 0.75rem; +.settings-label { + font-weight: bold; + text-transform: uppercase; + display: inline-block; } -.tsd-theme-toggle > h4 { - display: inline; - vertical-align: middle; - margin-right: 0.75rem; + +.tsd-filter-visibility .settings-label { + margin: 0.75rem 0 0.5rem 0; +} + +.tsd-theme-toggle .settings-label { + margin: 0.75rem 0.75rem 0 0; } .tsd-hierarchy { @@ -769,6 +777,9 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { padding: 0; max-width: 100%; } +.tsd-navigation .tsd-nav-link { + display: none; +} .tsd-nested-navigation { margin-left: 3rem; } @@ -782,6 +793,15 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { margin-left: -1.5rem; } +.tsd-page-navigation-section { + margin-left: 10px; +} +.tsd-page-navigation-section > summary { + padding: 0.25rem; +} +.tsd-page-navigation-section > div { + margin-left: 20px; +} .tsd-page-navigation ul { padding-left: 1.75rem; } @@ -812,10 +832,10 @@ a.tsd-index-link { } .tsd-accordion-summary, .tsd-accordion-summary a { - user-select: none; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; + user-select: none; cursor: pointer; } @@ -828,8 +848,9 @@ a.tsd-index-link { padding-top: 0; padding-bottom: 0; } -.tsd-index-accordion .tsd-accordion-summary > svg { +.tsd-accordion .tsd-accordion-summary > svg { margin-left: 0.25rem; + vertical-align: text-top; } .tsd-index-content > :not(:first-child) { margin-top: 0.75rem; @@ -877,7 +898,7 @@ a.tsd-index-link { } .tsd-panel-group { - margin: 4rem 0; + margin: 2rem 0; } .tsd-panel-group.tsd-index-group { margin: 2rem 0; @@ -885,6 +906,9 @@ a.tsd-index-link { .tsd-panel-group.tsd-index-group details { margin: 2rem 0; } +.tsd-panel-group > .tsd-accordion-summary { + margin-bottom: 1rem; +} #tsd-search { transition: background-color 0.2s; @@ -1034,6 +1058,12 @@ a.tsd-index-link { border-width: 1px 0; transition: background-color 0.1s; } +.tsd-signatures .tsd-index-signature:not(:last-child) { + margin-bottom: 1em; +} +.tsd-signatures .tsd-index-signature .tsd-signature { + border-width: 1px; +} .tsd-description .tsd-signatures .tsd-signature { border-width: 1px; } @@ -1347,6 +1377,12 @@ img { .has-menu .tsd-navigation { max-height: 100%; } + #tsd-toolbar-links { + display: none; + } + .tsd-navigation .tsd-nav-link { + display: flex; + } } /* one sidebar */ diff --git a/tsconfig.json b/tsconfig.json index 5de076553..70152b588 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,11 +6,11 @@ // Add our `ts` internal types "typeRoots": ["node_modules/@types", "src/lib/types"], - "types": ["node", "lunr", "marked", "mocha"], + "types": ["node", "lunr", "mocha"], // Speed up dev compilation time "incremental": true, "tsBuildInfoFile": "dist/.tsbuildinfo", - // "skipLibCheck": true, + "skipLibCheck": false, "strict": true, "alwaysStrict": true, @@ -34,12 +34,6 @@ "jsx": "react", "jsxFactory": "JSX.createElement", "jsxFragmentFactory": "JSX.Fragment", - - // TS 5 introduced verbatimModuleSyntax and deprecated importsNotUsedAsValues - // But that flag is intentionally very unfriendly to projects emitting CommonJS - // so for now, we're going to ignore that deprecation. - "ignoreDeprecations": "5.0", - "importsNotUsedAsValues": "error", "isolatedModules": true }, "include": ["src"], diff --git a/tsdoc.json b/tsdoc.json index 5e5590ec3..9bed22577 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -1,11 +1,20 @@ { "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + // If updating this, also update tsdoc-defaults.ts "noStandardTags": false, "tagDefinitions": [ + { + "tagName": "@author", + "syntaxKind": "block" + }, { "tagName": "@module", "syntaxKind": "block" }, + { + "tagName": "@type", + "syntaxKind": "block" + }, { "tagName": "@typedef", "syntaxKind": "block" @@ -58,6 +67,19 @@ "tagName": "@class", "syntaxKind": "modifier" }, + { + "tagName": "@document", + "syntaxKind": "block" + }, + { + "tagName": "@default", + "syntaxKind": "block" + }, + { + // TSDoc defines @returns, we also recognize @return for JSDoc compat + "tagName": "@return", + "syntaxKind": "block" + }, { "tagName": "@enum", "syntaxKind": "modifier" @@ -78,7 +100,7 @@ }, { "tagName": "@linkplain", - "syntaxKind": "block", + "syntaxKind": "inline", "allowMultiple": true }, { @@ -93,6 +115,18 @@ "tagName": "@satisfies", "syntaxKind": "block" }, + { + "tagName": "@since", + "syntaxKind": "block" + }, + { + "tagName": "@license", + "syntaxKind": "block" + }, + { + "tagName": "@import", + "syntaxKind": "block" + }, { "tagName": "@overload", "syntaxKind": "modifier" @@ -120,6 +154,14 @@ { "tagName": "@hideGroups", "syntaxKind": "modifier" + }, + { + "tagName": "@hideconstructor", + "syntaxKind": "modifier" + }, + { + "tagName": "@jsx", + "syntaxKind": "block" } ] }