diff --git a/.editorconfig b/.editorconfig index 220e0298e..a8cde4620 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,6 @@ trim_trailing_whitespace = true [package.json] indent_size = 2 + +[src/test/converter2/issues/gh2631/crlf.md] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes index e77811b3a..06874f468 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ # Disable core.autocrlf's line ending conversion behavior to prevent test failures on Windows * text=auto eol=lf +src/test/converter2/issues/gh2631/crlf.md text=auto eol=crlf *.png binary *.psd binary diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ccd5abf8..0e1d9226b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Unreleased +## v0.26.4 (2024-07-10) + +### Bug Fixes + +- The page navigation sidebar no longer incorrectly includes re-exports if the same member is exported with multiple names #2625. +- Page navigation now ensures the current page is visible when the page is first loaded, #2626. +- If a relative linked image is referenced multiple times, TypeDoc will no longer sometimes produce invalid links to the image #2627. +- `@link` tags will now be validated in referenced markdown documents, #2629. +- `@link` tags are now resolved in project documents, #2629. +- HTML/JSON output generated by TypeDoc now contains a trailing newline, #2632. +- TypeDoc now correctly handles markdown documents with CRLF line endings, #2628. +- `@hidden` is now properly applied when placed in a function implementation comment, #2634. +- Comments on re-exports are now rendered. + +### Thanks! + +- @bukowa +- @garrett-hopper + ## v0.26.3 (2024-06-28) ### Features diff --git a/internal-docs/plugins.md b/internal-docs/plugins.md index 57110eca2..3520453d1 100644 --- a/internal-docs/plugins.md +++ b/internal-docs/plugins.md @@ -2,13 +2,14 @@ title: Plugins children: - ./components-and-events.md + - ./third-party-symbols.md --- # Writing a TypeDoc Plugin TypeDoc supports plugins which can modify how projects are converted, how converted symbols are organized, and how they are displayed, among other things. Plugins are Node modules which -export a single `load` function that will be called by TypeDoc with the [Application] instance +export a single `load` function that will be called by TypeDoc with the {@link Application} instance which they are to be attached to. Plugins should assume that they may be loaded multiple times for different applications, and that a single load of an application class may be used to convert multiple projects. @@ -26,8 +27,8 @@ export function load(app) { ``` Plugins affect TypeDoc's execution by attaching event listeners to one or many events that will be -fired during conversion and rendering. Events are available on the [Application], [Converter], -[Renderer], and [Serializer]/[Deserializer] classes. There are static `EVENT_*` properties on those +fired during conversion and rendering. Events are available on the {@link Application}, {@link Converter}, +{@link Renderer}, and {@link Serializer}/{@link Deserializer} classes. There are static `EVENT_*` properties on those classes which describe the available events. The best way to learn what's available to plugins is to browse the docs, or look at the source code @@ -45,10 +46,5 @@ TypeDoc works. The [development page](https://typedoc.org/guides/development/) o If you have specific questions regarding plugin development, please open an issue or ask in the [TypeScript Discord] #typedoc channel. -[Application]: https://typedoc.org/api/classes/Application.html -[Converter]: https://typedoc.org/api/classes/Converter.html -[Renderer]: https://typedoc.org/api/classes/Renderer.html -[Serializer]: https://typedoc.org/api/classes/Serializer.html -[Deserializer]: https://typedoc.org/api/classes/Deserializer.html [typedoc-plugin-mdn-links]: https://github.com/Gerrit0/typedoc-plugin-mdn-links/blob/main/src/index.ts [TypeScript Discord]: https://discord.gg/typescript diff --git a/internal-docs/third-party-symbols.md b/internal-docs/third-party-symbols.md index bf7a1caee..eab94b910 100644 --- a/internal-docs/third-party-symbols.md +++ b/internal-docs/third-party-symbols.md @@ -1,3 +1,7 @@ +--- +title: Third Party Symbols +--- + # Third Party Symbols TypeDoc 0.22 added support for linking to third party sites by associating a symbol name with npm packages. @@ -47,7 +51,8 @@ A wildcard can be used to provide a fallback link to any unmapped type. } ``` -Plugins can add support for linking to third party sites by calling [`app.converter.addUnknownSymbolResolver`][addUnknownSymbolResolver]. +Plugins can add support for linking to third party sites by calling +{@link Converter.addUnknownSymbolResolver | `app.converter.addUnknownSymbolResolver`} If the given symbol is unknown, or does not appear in the documentation site, the resolver may return `undefined` and no link will be rendered unless provided by another resolver. @@ -150,12 +155,10 @@ export function load(app: Application) { ``` The unknown symbol resolver will also be passed the reflection containing the link -and, if the link was defined by the user, the [CommentDisplayPart] which was parsed into the [DeclarationReference] provided as the first argument. +and, if the link was defined by the user, the {@link Models.CommentDisplayPart} which was parsed into the +{@link DeclarationReference} provided as the first argument. -If `--useTsLinkResolution` is on (the default), it may also be passed a [ReflectionSymbolId] referencing the symbol that TypeScript resolves the link to. +If `--useTsLinkResolution` is on (the default), it may also be passed a {@link Models.ReflectionSymbolId} +referencing the symbol that TypeScript resolves the link to. [externalSymbolLinkMappings]: https://typedoc.org/options/comments/#externalsymbollinkmappings -[CommentDisplayPart]: https://typedoc.org/api/types/CommentDisplayPart.html -[DeclarationReference]: https://typedoc.org/api/interfaces/DeclarationReference.html -[ReflectionSymbolId]: https://typedoc.org/api/classes/Application.html -[addUnknownSymbolResolver]: https://typedoc.org/api/classes/Converter.html#addUnknownSymbolResolver diff --git a/package-lock.json b/package-lock.json index 769a7e17e..b52c35f67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typedoc", - "version": "0.26.3", + "version": "0.26.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typedoc", - "version": "0.26.3", + "version": "0.26.4", "license": "Apache-2.0", "dependencies": { "lunr": "^2.3.9", diff --git a/package.json b/package.json index a701065dd..f280521c2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "typedoc", "description": "Create api documentation for TypeScript projects.", - "version": "0.26.3", + "version": "0.26.4", "homepage": "https://typedoc.org", "exports": { ".": "./dist/index.js", diff --git a/src/index.ts b/src/index.ts index 5ddcd3e9b..9a20cec8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,6 +49,7 @@ export type { RendererHooks, NavigationElement, RendererEvents, + PageHeading, } from "./lib/output"; export { diff --git a/src/lib/application.ts b/src/lib/application.ts index b5a323e18..7b15a5293 100644 --- a/src/lib/application.ts +++ b/src/lib/application.ts @@ -614,7 +614,7 @@ export class Application extends ChildableComponent< const ser = this.serializer.projectToObject(project, process.cwd()); const space = this.options.getValue("pretty") ? "\t" : ""; - await writeFile(out, JSON.stringify(ser, null, space)); + await writeFile(out, JSON.stringify(ser, null, space) + "\n"); this.logger.info(this.i18n.json_written_to_0(nicePath(out))); this.logger.verbose(`JSON rendering took ${Date.now() - start}ms`); } diff --git a/src/lib/converter/plugins/CommentPlugin.ts b/src/lib/converter/plugins/CommentPlugin.ts index 27f52f18b..33b622c80 100644 --- a/src/lib/converter/plugins/CommentPlugin.ts +++ b/src/lib/converter/plugins/CommentPlugin.ts @@ -634,14 +634,12 @@ export class CommentPlugin extends ConverterComponent { (comment.hasModifier("@internal") && this.excludeInternal); if ( - isHidden && + !isHidden && reflection.kindOf(ReflectionKind.ContainsCallSignatures) ) { return (reflection as DeclarationReflection) .getNonIndexSignatures() - .every((sig) => { - return !sig.comment || this.isHidden(sig); - }); + .every((sig) => this.isHidden(sig)); } return isHidden; diff --git a/src/lib/converter/plugins/LinkResolverPlugin.ts b/src/lib/converter/plugins/LinkResolverPlugin.ts index dcc0c840a..6a114b90d 100644 --- a/src/lib/converter/plugins/LinkResolverPlugin.ts +++ b/src/lib/converter/plugins/LinkResolverPlugin.ts @@ -4,7 +4,6 @@ import { ConverterEvents } from "../converter-events"; import { Option, type ValidationOptions } from "../../utils"; import { ContainerReflection, - DeclarationReflection, makeRecursiveVisitor, type ProjectReflection, type Reflection, @@ -57,14 +56,18 @@ export class LinkResolverPlugin extends ConverterComponent { }, }), ); + + if (reflection.readme) { + reflection.readme = this.owner.resolveLinks( + reflection.readme, + reflection, + ); + } } - if ( - reflection instanceof DeclarationReflection && - reflection.readme - ) { - reflection.readme = this.owner.resolveLinks( - reflection.readme, + if (reflection.isDocument()) { + reflection.content = this.owner.resolveLinks( + reflection.content, reflection, ); } diff --git a/src/lib/internationalization/translatable.ts b/src/lib/internationalization/translatable.ts index 67fb8fd72..9a452cb93 100644 --- a/src/lib/internationalization/translatable.ts +++ b/src/lib/internationalization/translatable.ts @@ -103,6 +103,8 @@ export const translatable = { 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}"`, + failed_to_resolve_link_to_0_in_document_1: `Failed to resolve link to "{0}" in document {1}`, + failed_to_resolve_link_to_0_in_document_1_may_have_meant_2: `Failed to resolve link to "{0}" in document {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: diff --git a/src/lib/models/FileRegistry.ts b/src/lib/models/FileRegistry.ts index cedd8add3..d55de0a9a 100644 --- a/src/lib/models/FileRegistry.ts +++ b/src/lib/models/FileRegistry.ts @@ -61,6 +61,10 @@ export class FileRegistry { const absolute = this.mediaToPath.get(id); if (!absolute) return; + if (this.names.has(id)) { + return this.names.get(id); + } + const file = basename(absolute); if (!this.nameUsage.has(file)) { this.nameUsage.set(file, 1); diff --git a/src/lib/output/index.ts b/src/lib/output/index.ts index 9046992b3..411ab95de 100644 --- a/src/lib/output/index.ts +++ b/src/lib/output/index.ts @@ -1,4 +1,10 @@ -export { PageEvent, RendererEvent, MarkdownEvent, IndexEvent } from "./events"; +export { + PageEvent, + RendererEvent, + MarkdownEvent, + IndexEvent, + type PageHeading, +} from "./events"; export { UrlMapping } from "./models/UrlMapping"; export type { RenderTemplate } from "./models/UrlMapping"; export { Renderer, type RendererEvents } from "./renderer"; diff --git a/src/lib/output/themes/default/DefaultTheme.tsx b/src/lib/output/themes/default/DefaultTheme.tsx index 553936c8a..f36410116 100644 --- a/src/lib/output/themes/default/DefaultTheme.tsx +++ b/src/lib/output/themes/default/DefaultTheme.tsx @@ -16,7 +16,7 @@ import { type RenderTemplate, UrlMapping } from "../../models/UrlMapping"; import type { PageEvent } from "../../events"; import type { MarkedPlugin } from "../../plugins"; import { DefaultThemeRenderContext } from "./DefaultThemeRenderContext"; -import { JSX } from "../../../utils"; +import { filterMap, JSX } from "../../../utils"; import { classNames, getDisplayName, getHierarchyRoots, toStyleClass } from "../lib"; import { icons } from "./partials/icon"; @@ -291,7 +291,7 @@ export class DefaultTheme extends Theme { render(page: PageEvent, template: RenderTemplate>): string { const templateOutput = this.defaultLayoutTemplate(page, template); - return "" + JSX.renderElement(templateOutput); + return "" + JSX.renderElement(templateOutput) + "\n"; } private _navigationCache: NavigationElement[] | undefined; @@ -319,20 +319,29 @@ export class DefaultTheme extends Theme { function toNavigation( element: ReflectionCategory | ReflectionGroup | DeclarationReflection | DocumentReflection, - ): NavigationElement { + ): NavigationElement | undefined { + const children = getNavigationElements(element); if (element instanceof ReflectionCategory || element instanceof ReflectionGroup) { + if (!children?.length) { + return; + } + return { text: element.title, - children: getNavigationElements(element), + children, }; } + if (!element.hasOwnDocument) { + return; + } + return { text: getDisplayName(element), path: element.url, kind: element.kind, class: classNames({ deprecated: element.isDeprecated() }, theme.getReflectionClasses(element)), - children: getNavigationElements(element), + children: children?.length ? children : undefined, }; } @@ -345,14 +354,14 @@ export class DefaultTheme extends Theme { | DocumentReflection, ): undefined | NavigationElement[] { if (parent instanceof ReflectionCategory) { - return parent.children.map(toNavigation); + return filterMap(parent.children, toNavigation); } if (parent instanceof ReflectionGroup) { if (shouldShowCategories(parent.owningReflection, opts) && parent.categories) { - return parent.categories.map(toNavigation); + return filterMap(parent.categories, toNavigation); } - return parent.children.map(toNavigation); + return filterMap(parent.children, toNavigation); } if (leaves.includes(parent.getFullName())) { @@ -364,28 +373,28 @@ export class DefaultTheme extends Theme { } if (parent.isDocument()) { - return parent.children?.map(toNavigation); + return filterMap(parent.children, 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); + return filterMap(parent.documents, toNavigation); } if (parent.categories && shouldShowCategories(parent, opts)) { - return parent.categories.map(toNavigation); + return filterMap(parent.categories, toNavigation); } if (parent.groups && shouldShowGroups(parent, opts)) { - return parent.groups.map(toNavigation); + return filterMap(parent.groups, toNavigation); } if (opts.includeFolders && parent.childrenIncludingDocuments?.some((child) => child.name.includes("/"))) { return deriveModuleFolders(parent.childrenIncludingDocuments); } - return parent.childrenIncludingDocuments?.map(toNavigation); + return filterMap(parent.childrenIncludingDocuments, toNavigation); } function deriveModuleFolders(children: Array) { @@ -414,12 +423,14 @@ export class DefaultTheme extends Theme { // Note: This might end up putting a module within another module if we document // both foo/index.ts and foo/bar.ts. - for (const child of children) { - const parts = child.name.split("/"); - const collection = resolveOrCreateParents(parts); + for (const child of children.filter((c) => c.hasOwnDocument)) { const nav = toNavigation(child); - nav.text = parts[parts.length - 1]; - collection.push(nav); + if (nav) { + const parts = child.name.split("/"); + const collection = resolveOrCreateParents(parts); + nav.text = parts[parts.length - 1]; + collection.push(nav); + } } // Now merge single-possible-paths together so we don't have folders in our navigation diff --git a/src/lib/output/themes/default/assets/typedoc/Application.ts b/src/lib/output/themes/default/assets/typedoc/Application.ts index 66fb784f8..45296947b 100644 --- a/src/lib/output/themes/default/assets/typedoc/Application.ts +++ b/src/lib/output/themes/default/assets/typedoc/Application.ts @@ -107,13 +107,15 @@ export class Application { iter = iter.parentElement; } - if (pageLink && !pageLink.checkVisibility()) { + if (pageLink && !checkVisible(pageLink)) { const top = pageLink.getBoundingClientRect().top - document.documentElement.clientHeight / 4; // If we are showing three columns, this will scroll the site menu down to // show the page we just loaded in the navigation. document.querySelector(".site-menu")!.scrollTop = top; + // If we are showing two columns + document.querySelector(".col-sidebar")!.scrollTop = top; } } @@ -218,3 +220,13 @@ export class Application { }); } } + +// https://stackoverflow.com/a/5354536/7186598 +function checkVisible(elm: Element) { + const rect = elm.getBoundingClientRect(); + const viewHeight = Math.max( + document.documentElement.clientHeight, + window.innerHeight, + ); + return !(rect.bottom < 0 || rect.top - viewHeight >= 0); +} diff --git a/src/lib/output/themes/default/partials/member.reference.tsx b/src/lib/output/themes/default/partials/member.reference.tsx index 709ec1388..a9fed8745 100644 --- a/src/lib/output/themes/default/partials/member.reference.tsx +++ b/src/lib/output/themes/default/partials/member.reference.tsx @@ -2,13 +2,18 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext"; import { JSX } from "../../../../utils"; import type { ReferenceReflection } from "../../../../models"; -export const memberReference = ({ urlTo, i18n }: DefaultThemeRenderContext, props: ReferenceReflection) => { +export const memberReference = ( + { urlTo, i18n, commentSummary, commentTags }: DefaultThemeRenderContext, + props: ReferenceReflection, +) => { const referenced = props.tryGetTargetReflectionDeep(); if (!referenced) { return ( <> {i18n.theme_re_exports()} {props.name} + {commentSummary(props)} + {commentTags(props)} ); } @@ -17,6 +22,8 @@ export const memberReference = ({ urlTo, i18n }: DefaultThemeRenderContext, prop return ( <> {i18n.theme_re_exports()} {referenced.name} + {commentSummary(props)} + {commentTags(props)} ); } @@ -24,6 +31,8 @@ export const memberReference = ({ urlTo, i18n }: DefaultThemeRenderContext, prop return ( <> {i18n.theme_renames_and_re_exports()} {referenced.name} + {commentSummary(props)} + {commentTags(props)} ); }; diff --git a/src/lib/utils/array.ts b/src/lib/utils/array.ts index ff79d13aa..128e8cb8b 100644 --- a/src/lib/utils/array.ts +++ b/src/lib/utils/array.ts @@ -132,12 +132,12 @@ export function* zip[]>( } export function filterMap( - iter: Iterable, + iter: Iterable | undefined, fn: (item: T) => U | undefined, ): U[] { const result: U[] = []; - for (const item of iter) { + for (const item of iter || []) { const newItem = fn(item); if (newItem !== void 0) { result.push(newItem); diff --git a/src/lib/utils/minimalSourceFile.ts b/src/lib/utils/minimalSourceFile.ts index f597b96b8..6469de5e1 100644 --- a/src/lib/utils/minimalSourceFile.ts +++ b/src/lib/utils/minimalSourceFile.ts @@ -7,10 +7,20 @@ import { binaryFindPartition } from "./array"; const lineStarts = new WeakMap(); export class MinimalSourceFile implements SourceFileLike { + readonly text: string; constructor( - readonly text: string, + text: string, readonly fileName: string, ) { + // This is unfortunate, but the yaml library we use relies on the source + // text using LF line endings https://github.com/eemeli/yaml/issues/127. + // If we don't do this, in a simple document which includes a single key + // like: + // --- + // title: Windows line endings + // --- + // we'll end up with a parsed title of "Windows line endings\r" + this.text = text.replaceAll("\r\n", "\n"); lineStarts.set(this, [0]); } diff --git a/src/lib/validation/links.ts b/src/lib/validation/links.ts index 3a2c103d3..dd63a1fdc 100644 --- a/src/lib/validation/links.ts +++ b/src/lib/validation/links.ts @@ -1,4 +1,5 @@ import { + type Reflection, ReflectionKind, type Comment, type CommentDisplayPart, @@ -17,7 +18,7 @@ function getBrokenPartLinks(parts: readonly CommentDisplayPart[]) { linkTags.includes(part.tag) && !part.target ) { - links.push(part.text); + links.push(part.text.trim()); } } @@ -40,39 +41,47 @@ export function validateLinks( logger: Logger, ): void { for (const id in project.reflections) { - const reflection = project.reflections[id]; + checkReflection(project.reflections[id], logger); + } + + if (!(project.id in project.reflections)) { + checkReflection(project, logger); + } +} - 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(), - ), - ); - } +function checkReflection(reflection: Reflection, logger: Logger) { + 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)) { + if (reflection.isDocument()) { + for (const broken of getBrokenPartLinks(reflection.content)) { // #2360, "@" is a future reserved character in TSDoc component paths // If a link starts with it, and doesn't include a module source indicator "!" // then the user probably is trying to link to a package containing "@" with an absolute link. if (broken.startsWith("@") && !broken.includes("!")) { logger.warn( - logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1_may_have_meant_2( + logger.i18n.failed_to_resolve_link_to_0_in_document_1_may_have_meant_2( broken, reflection.getFriendlyFullName(), broken.replace(/[.#~]/, "!"), @@ -80,39 +89,61 @@ export function validateLinks( ); } else { logger.warn( - logger.i18n.failed_to_resolve_link_to_0_in_comment_for_1( + logger.i18n.failed_to_resolve_link_to_0_in_document_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(), - ), - ); - } + for (const broken of getBrokenLinks(reflection.comment)) { + // #2360, "@" is a future reserved character in TSDoc component paths + // If a link starts with it, and doesn't include a module source indicator "!" + // then the user probably is trying to link to a package containing "@" with an absolute link. + if (broken.startsWith("@") && !broken.includes("!")) { + logger.warn( + 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(), + ), + ); } } } diff --git a/src/test/comments.test.ts b/src/test/comments.test.ts index e15ce775f..d0f7f7946 100644 --- a/src/test/comments.test.ts +++ b/src/test/comments.test.ts @@ -1509,6 +1509,7 @@ describe("Comment Parser", () => { ] satisfies CommentDisplayPart[]); equal(files.getName(1), "&a.png"); + equal(files.getName(1), "&a.png"); }); }); diff --git a/src/test/converter2/issues/gh2631/crlf.md b/src/test/converter2/issues/gh2631/crlf.md new file mode 100644 index 000000000..aa988a781 --- /dev/null +++ b/src/test/converter2/issues/gh2631/crlf.md @@ -0,0 +1,5 @@ +--- +title: "Windows Line Endings" +--- + +This file contains CRLF line endings diff --git a/src/test/converter2/issues/gh2631/index.ts b/src/test/converter2/issues/gh2631/index.ts new file mode 100644 index 000000000..ed16886d6 --- /dev/null +++ b/src/test/converter2/issues/gh2631/index.ts @@ -0,0 +1,5 @@ +/** + * @module + * @document crlf.md + */ +export const a = 123; diff --git a/src/test/converter2/issues/gh2634.ts b/src/test/converter2/issues/gh2634.ts new file mode 100644 index 000000000..f13fb5be4 --- /dev/null +++ b/src/test/converter2/issues/gh2634.ts @@ -0,0 +1,23 @@ +/** + * @param a - Number param. + */ +export function hidden(a: number): void; + +/** + * @param a - String param. + */ +export function hidden(a: string): void; + +/** @hidden */ +export function hidden(a: string | number): void { + console.log(a); +} + +/** @hidden */ +export function implicitlyHidden(x: string): void; +/** @hidden */ +export function implicitlyHidden(x: number): void; +export function implicitlyHidden() {} + +/** @hidden */ +export const hiddenVariableFunc = () => 1; diff --git a/src/test/issues.c2.test.ts b/src/test/issues.c2.test.ts index bb3866389..99f827ae6 100644 --- a/src/test/issues.c2.test.ts +++ b/src/test/issues.c2.test.ts @@ -1636,4 +1636,17 @@ describe("Issue Tests", () => { logger.expectNoOtherMessages(); }); + + it("#2631 handles CRLF line endings in frontmatter", () => { + const project = convert(); + equal( + project.documents?.map((d) => d.name), + ["Windows Line Endings"], + ); + }); + + it("#2634 handles @hidden on function implementations", () => { + const project = convert(); + equal(project.children?.map((c) => c.name) || [], []); + }); });