Skip to content

Commit

Permalink
fix: prevent generation of empty references sidebar group
Browse files Browse the repository at this point in the history
  • Loading branch information
HiDeoo committed Jan 4, 2024
1 parent 5b2f32a commit e0217fe
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 32 deletions.
2 changes: 2 additions & 0 deletions fixtures/basics/src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { doThingA as doThingARef } from './functions'

/**
* This is a string variable.
*/
Expand Down
99 changes: 94 additions & 5 deletions packages/starlight-typedoc/libs/starlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ import path from 'node:path'

import type { StarlightPlugin } from '@astrojs/starlight/types'
import { slug } from 'github-slugger'
import { type DeclarationReflection, type ProjectReflection, ReflectionKind } from 'typedoc'
import {
type DeclarationReflection,
type ProjectReflection,
ReflectionKind,
ReferenceReflection,
type ReflectionGroup,
} from 'typedoc'

import type { StarlightTypeDocSidebarOptions } from '..'

const externalLinkRegex = /^(http|ftp)s?:\/\//

const sidebarDefaultOptions = {
collapsed: false,
label: 'API',
Expand All @@ -24,13 +32,13 @@ export function getSidebarFromReflections(
sidebar: StarlightUserConfigSidebar,
options: StarlightTypeDocSidebarOptions = {},
reflections: ProjectReflection | DeclarationReflection,
outputDirectory: string,
baseOutputDirectory: string,
): StarlightUserConfigSidebar {
if (!sidebar || sidebar.length === 0) {
return sidebar
}

const sidebarGroup = getSidebarGroupFromReflections(options, reflections, outputDirectory)
const sidebarGroup = getSidebarGroupFromReflections(options, reflections, baseOutputDirectory, baseOutputDirectory)

function replaceSidebarGroupPlaceholder(group: SidebarManualGroup): SidebarGroup {
if (group.label === starlightTypeDocSidebarGroupLabel.toString()) {
Expand All @@ -57,7 +65,7 @@ export function getSidebarFromReflections(
function getSidebarGroupFromPackageReflections(
options: StarlightTypeDocSidebarOptions,
reflections: ProjectReflection | DeclarationReflection,
outputDirectory: string,
baseOutputDirectory: string,
): SidebarGroup {
const groups = (reflections.children ?? []).map((child) => {
if (!child.url) {
Expand All @@ -66,7 +74,13 @@ function getSidebarGroupFromPackageReflections(

const url = path.parse(child.url)

return getSidebarGroupFromReflections(options, child, `${outputDirectory}/${url.dir}`, child.name)
return getSidebarGroupFromReflections(
options,
child,
baseOutputDirectory,
`${baseOutputDirectory}/${url.dir}`,
child.name,
)
})

return {
Expand All @@ -79,6 +93,7 @@ function getSidebarGroupFromPackageReflections(
function getSidebarGroupFromReflections(
options: StarlightTypeDocSidebarOptions,
reflections: ProjectReflection | DeclarationReflection,
baseOutputDirectory: string,
outputDirectory: string,
label?: string,
): SidebarGroup {
Expand All @@ -105,11 +120,16 @@ function getSidebarGroupFromReflections(
return getSidebarGroupFromReflections(
{ collapsed: true, label: child.name },
child,
baseOutputDirectory,
`${outputDirectory}/${isParentKindModule ? url.dir.split('/').slice(1).join('/') : url.dir}`,
)
})
}

if (isReferenceReflectionGroup(group)) {
return getReferencesSidebarGroup(group, baseOutputDirectory)
}

return {
collapsed: true,
label: group.title,
Expand All @@ -123,16 +143,85 @@ function getSidebarGroupFromReflections(
}
}

function getReferencesSidebarGroup(
group: ReflectionGroup,
baseOutputDirectory: string,
): SidebarManualGroup | undefined {
const referenceItems: LinkItem[] = group.children
.map((child) => {
const reference = child as ReferenceReflection
let target = reference.tryGetTargetReflectionDeep()

if (!target) {
return undefined
}

if (target.kindOf(ReflectionKind.TypeLiteral) && target.parent) {
target = target.parent
}

if (!target.url) {
return undefined
}

return {
label: reference.name,
link: getRelativeURL(target.url, getStarlightTypeDocOutputDirectory(baseOutputDirectory)),
}
})
.filter((item): item is LinkItem => item !== undefined)

if (referenceItems.length === 0) {
return undefined
}

return {
label: group.title,
items: referenceItems,
}
}

export function getAsideMarkdown(type: AsideType, title: string, content: string) {
return `:::${type}[${title}]
${content}
:::`
}

export function getRelativeURL(url: string | undefined, baseUrl: string): string | null {
if (!url) {
return null
} else if (externalLinkRegex.test(url)) {
return url
}

const filePath = path.parse(url)
const [, anchor] = filePath.base.split('#')
const segments = filePath.dir
.split('/')
.map((segment) => slug(segment))
.filter((segment) => segment !== '')

let constructedUrl = typeof baseUrl === 'string' ? baseUrl : ''
constructedUrl += segments.length > 0 ? `${segments.join('/')}/` : ''
constructedUrl += slug(filePath.name)
constructedUrl += '/'
constructedUrl += anchor && anchor.length > 0 ? `#${anchor}` : ''

return constructedUrl
}

export function getStarlightTypeDocOutputDirectory(outputDirectory: string, base = '') {
return path.posix.join(base, `/${outputDirectory}${outputDirectory.endsWith('/') ? '' : '/'}`)
}

function isSidebarManualGroup(item: NonNullable<StarlightUserConfigSidebar>[number]): item is SidebarManualGroup {
return 'items' in item
}

function isReferenceReflectionGroup(group: ReflectionGroup) {
return group.children.every((child) => child instanceof ReferenceReflection)
}

type SidebarGroup =
| SidebarManualGroup
| {
Expand Down
30 changes: 4 additions & 26 deletions packages/starlight-typedoc/libs/theme.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import path from 'node:path'

import { slug } from 'github-slugger'
import { Reflection, type Comment, type CommentTag, type Options, type PageEvent } from 'typedoc'
import { MarkdownTheme, MarkdownThemeRenderContext } from 'typedoc-plugin-markdown'

import { getAsideMarkdown } from './starlight'
import { getAsideMarkdown, getRelativeURL } from './starlight'

const customBlockTagTypes = ['@deprecated'] as const
const customModifiersTagTypes = ['@alpha', '@beta', '@experimental'] as const

const externalLinkRegex = /^(http|ftp)s?:\/\//

export class StarlightTypeDocTheme extends MarkdownTheme {
override getRenderContext(event: PageEvent<Reflection>) {
return new StarlightTypeDocThemeRenderContext(event, this.application.options)
Expand All @@ -26,27 +21,10 @@ class StarlightTypeDocThemeRenderContext extends MarkdownThemeRenderContext {
}

override relativeURL: (url: string | undefined) => string | null = (url) => {
if (!url) {
return null
} else if (externalLinkRegex.test(url)) {
return url
}
const outputDirectory = this.options.getValue('starlight-typedoc-output')
const baseUrl = typeof outputDirectory === 'string' ? outputDirectory : ''

const filePath = path.parse(url)
const [, anchor] = filePath.base.split('#')
const segments = filePath.dir
.split('/')
.map((segment) => slug(segment))
.filter((segment) => segment !== '')
const baseUrl = this.options.getValue('starlight-typedoc-output')

let constructedUrl = typeof baseUrl === 'string' ? baseUrl : ''
constructedUrl += segments.length > 0 ? `${segments.join('/')}/` : ''
constructedUrl += slug(filePath.name)
constructedUrl += '/'
constructedUrl += anchor && anchor.length > 0 ? `#${anchor}` : ''

return constructedUrl
return getRelativeURL(url, baseUrl)
}

override comment: (
Expand Down
3 changes: 2 additions & 1 deletion packages/starlight-typedoc/libs/typedoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { StarlightTypeDocOptions } from '..'

import { StarlightTypeDocLogger } from './logger'
import { addFrontmatter } from './markdown'
import { getStarlightTypeDocOutputDirectory } from './starlight'
import { StarlightTypeDocTheme } from './theme'

const defaultTypeDocConfig: TypeDocConfig = {
Expand Down Expand Up @@ -92,7 +93,7 @@ async function bootstrapApp(
onRendererPageEnd(event, pagination)
})
app.options.addDeclaration({
defaultValue: path.posix.join(base, `/${outputDirectory}${outputDirectory.endsWith('/') ? '' : '/'}`),
defaultValue: getStarlightTypeDocOutputDirectory(outputDirectory, base),
help: 'The starlight-typedoc output directory containing the generated documentation markdown files relative to the `src/content/docs/` directory.',
name: 'starlight-typedoc-output',
type: ParameterType.String,
Expand Down
5 changes: 5 additions & 0 deletions packages/starlight-typedoc/tests/e2e/basics/sidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ test('should generate the proper items for for a single entry point', async ({ d
const items = await docPage.getTypeDocSidebarItems()

expect(items).toMatchObject([
{
label: 'References',
items: [{ name: 'doThingARef' }],
collapsed: true,
},
{
label: 'Enumerations',
items: [{ name: 'ANumericEnum' }, { name: 'AStringEnum' }],
Expand Down

0 comments on commit e0217fe

Please sign in to comment.