From e8b8e51a63202da965d7342c47559bd280c4b612 Mon Sep 17 00:00:00 2001 From: Twilight <46562212+twlite@users.noreply.github.com> Date: Sun, 15 Dec 2024 01:01:17 +0545 Subject: [PATCH 01/12] chore: remove internal comment from jsdoc --- src/lib/utils/options/readers/typedoc.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/utils/options/readers/typedoc.ts b/src/lib/utils/options/readers/typedoc.ts index 5d7f88692..75d5d43d4 100644 --- a/src/lib/utils/options/readers/typedoc.ts +++ b/src/lib/utils/options/readers/typedoc.ts @@ -14,10 +14,9 @@ import type { TranslatedString } from "../../../internationalization/internation /** * Obtains option values from typedoc.json - * - * Changes need to happen here at some point. I think we should follow ESLint's new config - * system eventually: https://eslint.org/blog/2022/08/new-config-system-part-1/ */ +// Changes need to happen here at some point. I think we should follow ESLint's new config +// system eventually: https://eslint.org/blog/2022/08/new-config-system-part-1/ export class TypeDocReader implements OptionsReader { /** * Should run before the tsconfig reader so that it can specify a tsconfig file to read. From 4f518a6287afd9125a5ceff3ca00fb260501dcff Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Tue, 17 Dec 2024 20:06:25 -0700 Subject: [PATCH 02/12] Support `@include` in readme file Ref: #2814 --- CHANGELOG.md | 4 ++++ src/lib/converter/converter.ts | 5 ++++- src/lib/converter/plugins/IncludePlugin.ts | 2 +- src/lib/converter/plugins/PackagePlugin.ts | 8 ++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52dfb6042..2458b31f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ title: Changelog ## Unreleased +### Bug Fixes + +- `@include` and `@includeCode` now work in the readme file, #2814. + ## v0.27.5 (2024-12-14) ### Bug Fixes diff --git a/src/lib/converter/converter.ts b/src/lib/converter/converter.ts index 8d56499b6..75f3f2c56 100644 --- a/src/lib/converter/converter.ts +++ b/src/lib/converter/converter.ts @@ -248,6 +248,9 @@ export class Converter extends AbstractComponent { */ static readonly EVENT_RESOLVE_END = ConverterEvents.RESOLVE_END; + /** @internal @hidden */ + includePlugin: IncludePlugin; + constructor(owner: Application) { super(owner); @@ -304,7 +307,7 @@ export class Converter extends AbstractComponent { new PackagePlugin(this); new SourcePlugin(this); new TypePlugin(this); - new IncludePlugin(this); + this.includePlugin = new IncludePlugin(this); new MergeModuleWithPlugin(this); } diff --git a/src/lib/converter/plugins/IncludePlugin.ts b/src/lib/converter/plugins/IncludePlugin.ts index c7e43a19b..8e07c6aa3 100644 --- a/src/lib/converter/plugins/IncludePlugin.ts +++ b/src/lib/converter/plugins/IncludePlugin.ts @@ -46,7 +46,7 @@ export class IncludePlugin extends ConverterComponent { } } - private checkIncludeTagsParts( + checkIncludeTagsParts( refl: Reflection, relative: string, parts: CommentDisplayPart[], diff --git a/src/lib/converter/plugins/PackagePlugin.ts b/src/lib/converter/plugins/PackagePlugin.ts index a8f63a1e1..e14338492 100644 --- a/src/lib/converter/plugins/PackagePlugin.ts +++ b/src/lib/converter/plugins/PackagePlugin.ts @@ -135,6 +135,14 @@ export class PackagePlugin extends ConverterComponent { ); project.readme = content; + + // This isn't ideal, but seems better than figuring out the readme + // path over in the include plugin... + this.owner.includePlugin.checkIncludeTagsParts( + project, + Path.dirname(this.readmeFile), + content, + ); } if (this.packageJson) { From 016e6a169fc0930afc5e031726c2d397b496133c Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Tue, 17 Dec 2024 20:09:27 -0700 Subject: [PATCH 03/12] Lock npm to 10 for CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab820868b..9a26480d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: with: node-version: 18 - name: Upgrade npm - run: npm i -g npm@latest + run: npm i -g npm@10 - name: Install run: npm ci - name: Build From 2c10f676799e70f1fa5c45863e93c4cce9aa9de1 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Fri, 20 Dec 2024 08:17:38 -0700 Subject: [PATCH 04/12] Avoid references to references Resolves #2811 --- CHANGELOG.md | 1 + src/lib/converter/symbols.ts | 2 +- src/test/converter2/issues/gh2811.ts | 6 ++++++ src/test/issues.c2.test.ts | 12 ++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/test/converter2/issues/gh2811.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2458b31f5..f27a53844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ title: Changelog ### Bug Fixes - `@include` and `@includeCode` now work in the readme file, #2814. +- TypeDoc will now avoid making references to references, #2811. ## v0.27.5 (2024-12-14) diff --git a/src/lib/converter/symbols.ts b/src/lib/converter/symbols.ts index 73a22a012..fba8ef17e 100644 --- a/src/lib/converter/symbols.ts +++ b/src/lib/converter/symbols.ts @@ -929,7 +929,7 @@ function createAlias( // We already have this. Create a reference. const ref = new ReferenceReflection( exportSymbol?.name ?? symbol.name, - target, + target.isReference() ? target.getTargetReflection() : target, context.scope, ); context.postReflectionCreation(ref, symbol, exportSymbol); diff --git a/src/test/converter2/issues/gh2811.ts b/src/test/converter2/issues/gh2811.ts new file mode 100644 index 000000000..d439e6113 --- /dev/null +++ b/src/test/converter2/issues/gh2811.ts @@ -0,0 +1,6 @@ +export const abc = 123; + +export { abc as rename1 }; + +import { rename1 } from "./gh2811.js"; +export { rename1 as rename2 }; diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index fa0d03f06..c5bbfd76f 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -1971,4 +1971,16 @@ describe("Issue Tests", () => { equal(alpha2.type?.type, "reflection"); equal(alpha2.type.declaration.comment, undefined); }); + + it("#2811 avoids references to references", () => { + const project = convert(); + const abc = query(project, "abc"); + const rename1 = query(project, "rename1"); + ok(rename1.isReference()); + ok(rename1.getTargetReflection() === abc); + + const rename2 = query(project, "rename2"); + ok(rename2.isReference()); + ok(rename2.getTargetReflection() === abc); + }); }); From 23008f617c34144c7e7a963a5aca1b2b04104c3e Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Fri, 20 Dec 2024 15:35:09 -0700 Subject: [PATCH 05/12] Improve link resolution prioritization Ref: gerrit0/typedoc-plugin-zod#8 --- CHANGELOG.md | 2 ++ src/lib/converter/comments/linkResolver.ts | 35 ++++++++++++++++----- src/lib/utils/array.ts | 22 +++++++++++++ src/test/converter/exports/specs.json | 4 +-- src/test/converter/exports/specs.nodoc.json | 2 +- src/test/utils/array.test.ts | 21 +++++++++++++ 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27a53844..a6d75277b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ title: Changelog - `@include` and `@includeCode` now work in the readme file, #2814. - TypeDoc will now avoid making references to references, #2811. +- Improved link resolution logic to prioritize type alias properties with the + same symbol over type literal properties within function parameters. ## v0.27.5 (2024-12-14) diff --git a/src/lib/converter/comments/linkResolver.ts b/src/lib/converter/comments/linkResolver.ts index e2e082b34..c893c217c 100644 --- a/src/lib/converter/comments/linkResolver.ts +++ b/src/lib/converter/comments/linkResolver.ts @@ -13,6 +13,7 @@ import { parseDeclarationReference, } from "./declarationReference.js"; import { resolveDeclarationReference } from "./declarationReferenceResolver.js"; +import { maxElementByScore } from "../../utils/array.js"; const urlPrefix = /^(http|ftp)s?:\/\//; @@ -138,14 +139,32 @@ function resolveLinkTag( if (tsTargets.length) { // Find the target most likely to have a real url in the generated documentation - target = - tsTargets.find((r) => r.kindOf(ReflectionKind.SomeExport)) || - tsTargets.find( - (r) => - r.kindOf(ReflectionKind.SomeMember) && - r.parent?.kindOf(ReflectionKind.SomeExport), - ) || - tsTargets[0]; + // 1. A direct export (class, interface, variable) + // 2. A property of a direct export (class/interface property) + // 3. A property of a type of an export (property on type alias) + // 4. Whatever the first symbol found was + target = maxElementByScore(tsTargets, (r) => { + if (r.kindOf(ReflectionKind.SomeExport)) { + return 4; + } + if ( + r.kindOf(ReflectionKind.SomeMember) && + r.parent?.kindOf(ReflectionKind.SomeExport) + ) { + return 3; + } + if ( + r.kindOf(ReflectionKind.SomeMember) && + r.parent?.kindOf(ReflectionKind.TypeLiteral) && + r.parent.parent?.kindOf( + ReflectionKind.TypeAlias | ReflectionKind.Variable, + ) + ) { + return 2; + } + + return 1; + })!; pos = end; defaultDisplayText = part.tsLinkText || diff --git a/src/lib/utils/array.ts b/src/lib/utils/array.ts index 5a92f8542..82e85bea9 100644 --- a/src/lib/utils/array.ts +++ b/src/lib/utils/array.ts @@ -196,3 +196,25 @@ export function joinArray( } return ""; } + +export function maxElementByScore( + arr: readonly T[], + score: (a: T) => number, +): T | undefined { + if (arr.length === 0) { + return undefined; + } + + let largest = arr[0]; + let largestScore = score(arr[0]); + + for (let i = 1; i < arr.length; ++i) { + const itemScore = score(arr[i]); + if (itemScore > largestScore) { + largest = arr[i]; + largestScore = itemScore; + } + } + + return largest; +} diff --git a/src/test/converter/exports/specs.json b/src/test/converter/exports/specs.json index ecdf2a80c..180fc5089 100644 --- a/src/test/converter/exports/specs.json +++ b/src/test/converter/exports/specs.json @@ -308,7 +308,7 @@ "url": "typedoc://mod.ts#L13" } ], - "target": 31 + "target": 30 }, { "id": 40, @@ -412,7 +412,7 @@ "url": "typedoc://mod.ts#L40" } ], - "target": 34 + "target": 29 } ], "groups": [ diff --git a/src/test/converter/exports/specs.nodoc.json b/src/test/converter/exports/specs.nodoc.json index a56de8da7..5ab9aabda 100644 --- a/src/test/converter/exports/specs.nodoc.json +++ b/src/test/converter/exports/specs.nodoc.json @@ -34,7 +34,7 @@ "url": "typedoc://mod.ts#L13" } ], - "target": 31 + "target": 30 }, { "id": 43, diff --git a/src/test/utils/array.test.ts b/src/test/utils/array.test.ts index 3e3752fa5..d91a48659 100644 --- a/src/test/utils/array.test.ts +++ b/src/test/utils/array.test.ts @@ -3,6 +3,7 @@ import { binaryFindPartition, insertOrderSorted, insertPrioritySorted, + maxElementByScore, removeIfPresent, } from "../../lib/utils/array.js"; @@ -138,4 +139,24 @@ describe("Array utils", () => { equal(arr, [2, 1]); }); }); + + describe("maxElementByScore", () => { + it("Gets the max element", () => { + const arr = [1, 2, 3]; + const item = maxElementByScore(arr, (x) => x); + equal(item, 3); + }); + + it("Prioritizes elements earlier in the array", () => { + const arr = [1, 2, 3]; + const item = maxElementByScore(arr, () => 1); + equal(item, 1); + }); + + it("Returns undefined for an empty array", () => { + const arr: unknown[] = []; + const item = maxElementByScore(arr, () => 1); + equal(item, undefined); + }); + }); }); From b9177b46ac9246d4d955663e01fb523b649c23d0 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Fri, 20 Dec 2024 16:03:18 -0700 Subject: [PATCH 06/12] Update contributing docs Resolves #2815 --- .github/CONTRIBUTING.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 42b0a110b..11fc799df 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -93,12 +93,7 @@ If you don't already have [Git] installed, install it first. You will need it to ```bash npm run build ``` -1. Set up a baseline for visual theme testing: - ```bash - npm run test:visual - npm run test:visual:accept - ``` -1. Open the typedoc folder in your favorite editor. If you don't have one, try [Visual Studio Code][vscode] or [Atom] +1. Open the typedoc folder in your favorite editor. If you don't have one, try [Visual Studio Code][vscode] ## Linting, Building, and Testing @@ -119,14 +114,16 @@ To compile the TypeDoc source, run `npm run build`. This will start the TypeScri TypeDoc includes an extensive set of tests that describe its output. To validate any changes you have made run `npm test`. This will run a subset of TypeDoc's tests intended for quick development checks. Tests which take more than half a second are located in `src/test/slow`, and will only be run if you run `npm run test:full`. -These tests will also run the visual regression tests, failing if there are any changes. -TypeDoc also contains visual regression tests for comparing changes made to the themes. To test this, run `npm run test:visual`. If there are changes that you expect, run `npm run test:visual:accept` to save the new baseline. If you have changed the TypeDoc output, it will cause tests to fail. Once you have validated that the introduced changes were intended, run `npm run rebuild_specs` to update the spec files for the new output. Additional tests are contained within the `converter2` directory that look for specific issues with previous builds and may be more appropriate for adding a test for a bugfix. +#### Visual Regression Testing + +There is also a script to visually compare the differences between changes. To set this up, build the code, then run `node scripts/visual_regression.js`. This will create a baseline which future runs will be compared against after making changes, compiling, and running the visual regression script again. + ## Pull Requests Once you have finished working on an issue, you can submit a pull request to have your changes merged into the TypeDoc repository and included in the next release. @@ -150,4 +147,3 @@ If the TypeDoc repository has changed since you originally forked it, you will n [git]: https://git-scm.com [node]: https://nodejs.org/en/ [vscode]: https://code.visualstudio.com/ -[atom]: https://atom.io/ From 53fa22e577e174776c7c8f101c21c7c4e973a8c2 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Wed, 25 Dec 2024 16:27:33 -0700 Subject: [PATCH 07/12] Avoid including defaulted type arguments Resolves #2820 --- CHANGELOG.md | 1 + src/lib/converter/types.ts | 26 ++++++++++++++++++++------ src/lib/converter/utils/nodes.ts | 7 +++++++ src/test/converter/function/specs.json | 16 ---------------- src/test/converter2/issues/gh2820.ts | 3 +++ src/test/issues.c2.test.ts | 8 ++++++++ 6 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 src/test/converter2/issues/gh2820.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d75277b..419b923a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ title: Changelog - `@include` and `@includeCode` now work in the readme file, #2814. - TypeDoc will now avoid making references to references, #2811. +- Improved type reference conversion to avoid including defaulted type arguments, #2820. - Improved link resolution logic to prioritize type alias properties with the same symbol over type literal properties within function parameters. diff --git a/src/lib/converter/types.ts b/src/lib/converter/types.ts index 3b9b91592..316e6e6ac 100644 --- a/src/lib/converter/types.ts +++ b/src/lib/converter/types.ts @@ -38,7 +38,7 @@ import { createSignature, } from "./factories/signature.js"; import { convertSymbol } from "./symbols.js"; -import { isObjectType } from "./utils/nodes.js"; +import { isObjectType, isTypeReference } from "./utils/nodes.js"; import { removeUndefined } from "./utils/reflections.js"; import type { TranslatedString } from "../internationalization/internationalization.js"; @@ -813,6 +813,7 @@ const referenceConverter: TypeConverter< context, name, ); + if (type.flags & ts.TypeFlags.Substitution) { // NoInfer ref.typeArguments = [ @@ -823,11 +824,24 @@ const referenceConverter: TypeConverter< convertType(context, (type as ts.StringMappingType).type), ]; } else { - ref.typeArguments = ( - type.aliasSymbol - ? type.aliasTypeArguments - : (type as ts.TypeReference).typeArguments - )?.map((ref) => convertType(context, ref)); + // Default type arguments are filled with a reference to the default + // type. As TS doesn't support specifying earlier defaults, we know + // that this will only filter out type arguments which aren't specified + // by the user. + let ignoredArgs: ts.Type[] | undefined; + if (isTypeReference(type)) { + ignoredArgs = type.target.typeParameters + ?.map((p) => p.getDefault()) + .filter((x) => !!x); + } + + const args = type.aliasSymbol + ? type.aliasTypeArguments + : (type as ts.TypeReference).typeArguments; + + ref.typeArguments = args + ?.filter((ref) => !ignoredArgs?.includes(ref)) + .map((ref) => convertType(context, ref)); } return ref; }, diff --git a/src/lib/converter/utils/nodes.ts b/src/lib/converter/utils/nodes.ts index 728a8f8ae..fa772404d 100644 --- a/src/lib/converter/utils/nodes.ts +++ b/src/lib/converter/utils/nodes.ts @@ -37,3 +37,10 @@ export function getHeritageTypes( export function isObjectType(type: ts.Type): type is ts.ObjectType { return typeof (type as any).objectFlags === "number"; } + +export function isTypeReference(type: ts.Type): type is ts.TypeReference { + return ( + isObjectType(type) && + (type.objectFlags & ts.ObjectFlags.Reference) !== 0 + ); +} diff --git a/src/test/converter/function/specs.json b/src/test/converter/function/specs.json index 52b4ad862..83146c961 100644 --- a/src/test/converter/function/specs.json +++ b/src/test/converter/function/specs.json @@ -547,14 +547,6 @@ "name": "T", "package": "typedoc", "refersToTypeParameter": true - }, - { - "type": "intrinsic", - "name": "any" - }, - { - "type": "intrinsic", - "name": "any" } ], "name": "Iterable", @@ -705,14 +697,6 @@ "name": "T", "package": "typedoc", "refersToTypeParameter": true - }, - { - "type": "intrinsic", - "name": "any" - }, - { - "type": "intrinsic", - "name": "any" } ], "name": "Iterable", diff --git a/src/test/converter2/issues/gh2820.ts b/src/test/converter2/issues/gh2820.ts new file mode 100644 index 000000000..e6e5d8abd --- /dev/null +++ b/src/test/converter2/issues/gh2820.ts @@ -0,0 +1,3 @@ +export function f(a: Uint8Array): Uint8Array { + return a; +} diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index c5bbfd76f..ba8935568 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -1983,4 +1983,12 @@ describe("Issue Tests", () => { ok(rename2.isReference()); ok(rename2.getTargetReflection() === abc); }); + + it("#2820 does not include defaulted type arguments", () => { + const project = convert(); + const f = querySig(project, "f"); + + equal(f.type?.toString(), "Uint8Array"); + equal(f.parameters?.[0].type?.toString(), "Uint8Array"); + }); }); From 9b62f0947dfb88801abad981789adb5939af37cf Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Wed, 25 Dec 2024 16:31:25 -0700 Subject: [PATCH 08/12] Fix output specific option specification Resolves #2818 --- CHANGELOG.md | 1 + src/lib/application.ts | 4 ---- src/lib/utils/options/options.ts | 26 -------------------------- src/test/utils/options/options.test.ts | 8 -------- 4 files changed, 1 insertion(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419b923a7..6b38103a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ title: Changelog - `@include` and `@includeCode` now work in the readme file, #2814. - TypeDoc will now avoid making references to references, #2811. +- Fixed output specific option specification, #2818. - Improved type reference conversion to avoid including defaulted type arguments, #2820. - Improved link resolution logic to prioritize type alias properties with the same symbol over type literal properties within function parameters. diff --git a/src/lib/application.ts b/src/lib/application.ts index 483a52ee5..e17243c19 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -361,9 +361,6 @@ export class Application extends AbstractComponent< */ public async convert(): Promise { const start = Date.now(); - // We freeze here rather than in the Converter class since TypeDoc's tests reuse the Application - // with a few different settings. - this.options.freeze(); this.logger.verbose( `Using TypeScript ${this.getTypeScriptVersion()} from ${this.getTypeScriptPath()}`, ); @@ -431,7 +428,6 @@ export class Application extends AbstractComponent< public convertAndWatch( success: (project: ProjectReflection) => Promise, ): void { - this.options.freeze(); if ( !this.options.getValue("preserveWatchOutput") && this.logger instanceof ConsoleLogger diff --git a/src/lib/utils/options/options.ts b/src/lib/utils/options/options.ts index c3d68f423..701103889 100644 --- a/src/lib/utils/options/options.ts +++ b/src/lib/utils/options/options.ts @@ -133,20 +133,6 @@ export class Options { return options; } - /** - * Marks the options as readonly, enables caching when fetching options, which improves performance. - */ - freeze() { - Object.freeze(this._values); - } - - /** - * Checks if the options object has been frozen, preventing future changes to option values. - */ - isFrozen() { - return Object.isFrozen(this._values); - } - /** * Take a snapshot of option values now, used in tests only. * @internal @@ -318,12 +304,6 @@ export class Options { configPath?: NeverIfInternal, ): void; setValue(name: string, value: unknown, configPath?: string): void { - if (this.isFrozen()) { - throw new Error( - `Tried to modify an option (${name}) value after options have been frozen.`, - ); - } - const declaration = this.getDeclaration(name); if (!declaration) { const nearNames = this.getSimilarOptions(name); @@ -419,12 +399,6 @@ export class Options { options: ts.CompilerOptions, projectReferences: readonly ts.ProjectReference[] | undefined, ) { - if (this.isFrozen()) { - throw new Error( - "Tried to modify compiler options after options have been frozen.", - ); - } - // We do this here instead of in the tsconfig reader so that API consumers which // supply a program to `Converter.convert` instead of letting TypeDoc create one // can just set the compiler options, and not need to know about this mapping. diff --git a/src/test/utils/options/options.test.ts b/src/test/utils/options/options.test.ts index 6172ad916..c16eb931f 100644 --- a/src/test/utils/options/options.test.ts +++ b/src/test/utils/options/options.test.ts @@ -162,14 +162,6 @@ describe("Options", () => { throws(() => options.isSet("does not exist" as never)); }); - it("Throws if frozen and a value is set", () => { - const options = new Options(new Internationalization(null).proxy); - options.freeze(); - - throws(() => options.setValue("categorizeByGroup", true)); - throws(() => options.setCompilerOptions([], {}, [])); - }); - it("Supports resetting values", () => { const options = new Options(new Internationalization(null).proxy); From 9e667d06519f8c43df69ac0dbb3e6ac407ea1775 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Wed, 25 Dec 2024 16:41:48 -0700 Subject: [PATCH 09/12] Add ignoredHighlightLanguages option Resolves #2819 --- CHANGELOG.md | 5 +++++ example/src/documents/syntax-highlighting.md | 3 ++- site/options/output.md | 12 ++++++++++++ src/lib/internationalization/locales/en.cts | 2 ++ src/lib/output/renderer.ts | 4 ++++ src/lib/utils/highlighter.tsx | 12 +++++++++--- src/lib/utils/options/declaration.ts | 1 + src/lib/utils/options/defaults.ts | 2 ++ src/lib/utils/options/sources/typedoc.ts | 6 ++++++ 9 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b38103a9..941463bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ title: Changelog ## Unreleased +### Features + +- Added `ignoredHighlightLanguages` option to specify languages which will be + allowed in code blocks but not highlighted, #2819. + ### Bug Fixes - `@include` and `@includeCode` now work in the readme file, #2814. diff --git a/example/src/documents/syntax-highlighting.md b/example/src/documents/syntax-highlighting.md index 6bb96094a..b24674035 100644 --- a/example/src/documents/syntax-highlighting.md +++ b/example/src/documents/syntax-highlighting.md @@ -8,7 +8,8 @@ TypeDoc supports code blocks in Markdown and uses 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. +which languages are loaded for highlighting. The `ignoredHighlightLanguages` +option can be used to specify languages which should not be highlighted. If no language is specified, the code block is assumed to be TypeScript: diff --git a/site/options/output.md b/site/options/output.md index 4e280f7e1..b24aaa312 100644 --- a/site/options/output.md +++ b/site/options/output.md @@ -162,6 +162,18 @@ loads the following languages. } ``` +## ignoredHighlightLanguages + +Specifies languages used in code blocks which should be silently ignored by TypeDoc. +By default, TypeDoc will produce an error if a code block specifies a language which +is not present in the highlightLanguages array. + +```json +{ + "ignoredHighlightLanguages": ["mkdocs"] +} +``` + ## typePrintWidth Specifies the width at which to wrap code when rendering types, defaults to 80. diff --git a/src/lib/internationalization/locales/en.cts b/src/lib/internationalization/locales/en.cts index 8b38479a6..48c98b3d9 100644 --- a/src/lib/internationalization/locales/en.cts +++ b/src/lib/internationalization/locales/en.cts @@ -237,6 +237,8 @@ export = { 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_ignoredHighlightLanguages: + "Specify languages which will be accepted as valid highlight languages, but will not be highlighted at runtime", help_typePrintWidth: "Width at which to wrap code to a new line when rendering a type", help_customCss: "Path to a custom CSS file to for the theme to import", diff --git a/src/lib/output/renderer.ts b/src/lib/output/renderer.ts index 1df30cbdc..2e17b4de7 100644 --- a/src/lib/output/renderer.ts +++ b/src/lib/output/renderer.ts @@ -247,6 +247,9 @@ export class Renderer extends AbstractComponent { @Option("highlightLanguages") private accessor highlightLanguages!: string[]; + @Option("ignoredHighlightLanguages") + private accessor ignoredHighlightLanguages!: string[]; + @Option("pretty") private accessor pretty!: boolean; @@ -342,6 +345,7 @@ export class Renderer extends AbstractComponent { this.darkTheme, // Checked in option validation this.highlightLanguages as BundledLanguage[], + this.ignoredHighlightLanguages, ); } diff --git a/src/lib/utils/highlighter.tsx b/src/lib/utils/highlighter.tsx index 57b321706..a3b904824 100644 --- a/src/lib/utils/highlighter.tsx +++ b/src/lib/utils/highlighter.tsx @@ -116,14 +116,18 @@ class DoubleHighlighter { let shikiEngine: shiki.RegexEngine | undefined; let highlighter: DoubleHighlighter | undefined; +let ignoredLanguages: string[] | undefined; export async function loadHighlighter( lightTheme: shiki.BundledTheme, darkTheme: shiki.BundledTheme, langs: shiki.BundledLanguage[], + ignoredLangs: string[] | undefined, ) { if (highlighter) return; + ignoredLanguages = ignoredLangs; + if (!shikiEngine) { await shiki.loadBuiltinWasm(); shikiEngine = await shiki.createOnigurumaEngine(); @@ -138,7 +142,7 @@ export async function loadHighlighter( } export function isSupportedLanguage(lang: string) { - return getSupportedLanguages().includes(lang); + return ignoredLanguages?.includes(lang) || getSupportedLanguages().includes(lang); } export function getSupportedLanguages(): string[] { @@ -150,13 +154,15 @@ export function getSupportedThemes(): string[] { } export function isLoadedLanguage(lang: string): boolean { - return plaintextLanguages.includes(lang) || (highlighter?.supports(lang) ?? false); + return ( + plaintextLanguages.includes(lang) || ignoredLanguages?.includes(lang) || highlighter?.supports(lang) || false + ); } export function highlight(code: string, lang: string): string { assert(highlighter, "Tried to highlight with an uninitialized highlighter"); - if (plaintextLanguages.includes(lang)) { + if (plaintextLanguages.includes(lang) || ignoredLanguages?.includes(lang)) { return JSX.renderElement(<>{code}); } diff --git a/src/lib/utils/options/declaration.ts b/src/lib/utils/options/declaration.ts index a55b214bc..cd19ea7d1 100644 --- a/src/lib/utils/options/declaration.ts +++ b/src/lib/utils/options/declaration.ts @@ -145,6 +145,7 @@ export interface TypeDocOptionMap { lightHighlightTheme: ShikiTheme; darkHighlightTheme: ShikiTheme; highlightLanguages: string[]; + ignoredHighlightLanguages: string[]; typePrintWidth: number; customCss: string; customJs: string; diff --git a/src/lib/utils/options/defaults.ts b/src/lib/utils/options/defaults.ts index e5c269f47..89e4d5335 100644 --- a/src/lib/utils/options/defaults.ts +++ b/src/lib/utils/options/defaults.ts @@ -77,6 +77,8 @@ export const highlightLanguages: readonly BundledLanguage[] = [ "typescript", ]; +export const ignoredHighlightLanguages: readonly string[] = []; + export const sort: readonly string[] = [ "kind", "instance-first", diff --git a/src/lib/utils/options/sources/typedoc.ts b/src/lib/utils/options/sources/typedoc.ts index d862771bb..467426ee1 100644 --- a/src/lib/utils/options/sources/typedoc.ts +++ b/src/lib/utils/options/sources/typedoc.ts @@ -366,6 +366,12 @@ export function addTypeDocOptions(options: Pick) { } }, }); + options.addDeclaration({ + name: "ignoredHighlightLanguages", + help: (i18n) => i18n.help_ignoredHighlightLanguages(), + type: ParameterType.Array, + defaultValue: OptionDefaults.ignoredHighlightLanguages, + }); options.addDeclaration({ name: "typePrintWidth", help: (i18n) => i18n.help_typePrintWidth(), From d62646802456126e3697afee269520f7b02275a9 Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Wed, 25 Dec 2024 16:58:48 -0700 Subject: [PATCH 10/12] Fix `@link` with declaration reference using `!~` Resolves #2810 --- CHANGELOG.md | 1 + site/declaration-references.md | 5 +++++ src/lib/converter/comments/declarationReference.ts | 13 ++++++++++++- src/test/declarationReference.test.ts | 11 +++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941463bed..b49e12606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ title: Changelog - TypeDoc will now avoid making references to references, #2811. - Fixed output specific option specification, #2818. - Improved type reference conversion to avoid including defaulted type arguments, #2820. +- Fixed parsing of declaration references which include a module and a local reference, #2810. - Improved link resolution logic to prioritize type alias properties with the same symbol over type literal properties within function parameters. diff --git a/site/declaration-references.md b/site/declaration-references.md index 2e5668f3a..35068d360 100644 --- a/site/declaration-references.md +++ b/site/declaration-references.md @@ -48,6 +48,11 @@ The deliminator is used to determine how to navigate the project tree. | `#` | Indicates that the next component is a "member", including class instance properties, interface members, and enum members. | | `~` | Indicates that the next component is an export of a namespace/module. | +> [!warning] The TSDoc specification says that `~` traverses via locals. This is +> different than TypeDoc's behavior. TypeDoc will treat `~` as a stricter `.` +> which only supports navigating to a namespace/module export. It should +> generally be avoided in favor of `.` for improved compatibility with VSCode. + ```ts // module.ts /** diff --git a/src/lib/converter/comments/declarationReference.ts b/src/lib/converter/comments/declarationReference.ts index d54f72aae..aac97a8c9 100644 --- a/src/lib/converter/comments/declarationReference.ts +++ b/src/lib/converter/comments/declarationReference.ts @@ -65,7 +65,8 @@ export interface ComponentPath { * How to resolve the `path` * - `.` - Navigate via `exports` of symbol * - `#` - Navigate via `members` of symbol - * - `~` - Navigate via `locals` of symbol + * - `~` - Navigate via `locals` of symbol (note: TypeDoc does not support + * locals, see the declaration reference docs) */ navigation: "." | "#" | "~"; path: string; @@ -432,6 +433,7 @@ export function parseDeclarationReference( let moduleSource: string | undefined; let symbolReference: SymbolReference | undefined; let resolutionStart: "global" | "local" = "local"; + let topLevelLocalReference = false; const moduleSourceOrSymbolRef = parseModuleSource(source, pos, end); if (moduleSourceOrSymbolRef) { @@ -443,6 +445,12 @@ export function parseDeclarationReference( pos = moduleSourceOrSymbolRef[1] + 1; resolutionStart = "global"; moduleSource = moduleSourceOrSymbolRef[0]; + + // We might be referencing a local of a module + if (source[pos] === "~") { + topLevelLocalReference = true; + pos++; + } } } else if (source[pos] === "!") { pos++; @@ -452,6 +460,9 @@ export function parseDeclarationReference( const ref = parseSymbolReference(source, pos, end); if (ref) { symbolReference = ref[0]; + if (topLevelLocalReference && symbolReference.path?.length) { + symbolReference.path[0].navigation = "~"; + } pos = ref[1]; } diff --git a/src/test/declarationReference.test.ts b/src/test/declarationReference.test.ts index 578654cb6..49f71caef 100644 --- a/src/test/declarationReference.test.ts +++ b/src/test/declarationReference.test.ts @@ -215,5 +215,16 @@ describe("Declaration References", () => { equal(parse(""), undefined); equal(parse("@test/foo"), undefined); }); + + it("Handles module reference with top level ~ reference", () => { + equal(parse("mod!~foo"), { + moduleSource: "mod", + resolutionStart: "global", + symbolReference: { + path: [{ navigation: "~", path: "foo" }], + meaning: undefined, + }, + }); + }); }); }); From 44bffe80ebeede3dac1d9a970a43d5f2159af71e Mon Sep 17 00:00:00 2001 From: Gerrit Birkeland Date: Wed, 25 Dec 2024 17:02:05 -0700 Subject: [PATCH 11/12] Release v0.27.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc6208551..59a130198 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typedoc", - "version": "0.27.5", + "version": "0.27.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typedoc", - "version": "0.27.5", + "version": "0.27.6", "license": "Apache-2.0", "dependencies": { "@gerrit0/mini-shiki": "^1.24.0", diff --git a/package.json b/package.json index c482d304e..5d3c4f2e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typedoc", "description": "Create api documentation for TypeScript projects.", - "version": "0.27.5", + "version": "0.27.6", "homepage": "https://typedoc.org", "type": "module", "exports": { From 9a1a719380422a2d9901910bb9f74e4c8e3f5382 Mon Sep 17 00:00:00 2001 From: TypeDoc Bot Date: Thu, 26 Dec 2024 00:02:47 +0000 Subject: [PATCH 12/12] Update changelog for release --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b49e12606..300e04535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ title: Changelog ## Unreleased +## v0.27.6 (2024-12-26) + ### Features - Added `ignoredHighlightLanguages` option to specify languages which will be