Skip to content

Commit

Permalink
support passing a file path to APIReference
Browse files Browse the repository at this point in the history
  • Loading branch information
souporserious committed Oct 10, 2024
1 parent 8822ce6 commit fc1e9a6
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 34 deletions.
18 changes: 18 additions & 0 deletions .changeset/three-colts-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'renoun': minor
---

Adds support for passing a file path to the `APIReference.source` prop:

```tsx
import { APIReference } from 'renoun/components'

export function FilePath() {
return (
<APIReference
source="./GitProvider.tsx"
workingDirectory={import.meta.url}
/>
)
}
```
16 changes: 2 additions & 14 deletions packages/renoun/src/collections/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { resolveTsConfigPath } from '../utils/resolve-ts-config-path.js'
import type { SymbolFilter } from '../utils/resolve-type.js'
import { getSourcePathMap } from '../utils/get-source-files-path-map.js'
import { getSourceFilesOrderMap } from '../utils/get-source-files-sort-order.js'
import { getProject } from './project.js'

type GetImport = (slug: string) => Promise<any>

Expand Down Expand Up @@ -216,19 +217,6 @@ export interface CollectionOptions<Exports extends FileExports> {
}
}

const projectCache = new Map<string, Project>()

function resolveProject(tsConfigFilePath: string): Project {
if (!projectCache.has(tsConfigFilePath)) {
const project = new tsMorph.Project({
skipAddingFilesFromTsConfig: true,
tsConfigFilePath,
})
projectCache.set(tsConfigFilePath, project)
}
return projectCache.get(tsConfigFilePath)!
}

class Export<Value, AllExports extends FileExports = FileExports>
implements ExportSource<Value>
{
Expand Down Expand Up @@ -831,7 +819,7 @@ class Collection<AllExports extends FileExports>

this.options = options
this.getImport = getImport!
this.project = resolveProject(options.tsConfigFilePath)
this.project = getProject(options.tsConfigFilePath)

const compilerOptions = this.project.getCompilerOptions()
const tsConfigFilePath = String(compilerOptions.configFilePath)
Expand Down
56 changes: 56 additions & 0 deletions packages/renoun/src/collections/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Node, Project } from 'ts-morph'
import tsMorph from 'ts-morph'
import { resolve } from 'node:path'

import { resolveType } from '../project/index.js'
import type { SymbolFilter } from '../utils/resolve-type.js'

const projectCache = new Map<string, Project>()

export function getProject(
tsConfigFilePath: string = 'tsconfig.json'
): Project {
if (!projectCache.has(tsConfigFilePath)) {
const project = new tsMorph.Project({
skipAddingFilesFromTsConfig: true,
tsConfigFilePath,
})
projectCache.set(tsConfigFilePath, project)
}
return projectCache.get(tsConfigFilePath)!
}

export async function getExportedTypes(
filePath: string,
filter?: SymbolFilter,
workingDirectory: string = process.cwd(),
tsConfigFilePath: string = 'tsconfig.json'
) {
const project = getProject(tsConfigFilePath)

try {
const resolvedFilePath = resolve(workingDirectory, filePath)
const sourceFile = project.addSourceFileAtPath(resolvedFilePath)
const exportedDeclarations = Array.from(
sourceFile.getExportedDeclarations()
)

return Promise.all(
exportedDeclarations.flatMap(([, declarations]) => {
return declarations.flatMap((declaration) =>
resolveType({
declaration,
filter,
projectOptions: { tsConfigFilePath },
useClient: false,
})
)
})
)
} catch (error) {
throw new Error(
`Failed to add source file at path: ${filePath}\nCurrent working directory: ${workingDirectory}`,
{ cause: error }
)
}
}
11 changes: 11 additions & 0 deletions packages/renoun/src/components/APIReference.examples.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { APIReference } from 'renoun/components'

export function FilePath() {
return (
<APIReference
source="./GitProvider.tsx"
workingDirectory={import.meta.url}
/>
)
}
119 changes: 100 additions & 19 deletions packages/renoun/src/components/APIReference.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Fragment, Suspense } from 'react'
import type { CSSObject } from 'restyle'

import type { ExportSource } from '../collections/index.js'
import { getExportedTypes } from '../collections/project.js'
import { createSlug } from '../utils/create-slug.js'
import type {
AllTypes,
Expand All @@ -21,13 +22,108 @@ const mdxComponents = {
code: (props) => <MDXComponents.code {...props} paddingY="0" />,
} satisfies MDXComponents

interface SourceString {
/** The file path to the source code. */
source: string

/** The working directory to resolve the file path from. Will use the base URL if a URL is provided. */
workingDirectory?: string
}

interface SourceExport {
/** The export source from a collection export source to get types from. */
source: ExportSource<any>
}

interface Filter {
/** A filter to apply to the exported types. */
filter?: SymbolFilter
}

export type APIReferenceProps =
| (SourceString & Filter)
| (SourceExport & Filter)

/** Displays type documentation for all types exported from a file path or types related to a collection export source. */
export function APIReference(props: APIReferenceProps) {
return (
<Suspense fallback="Loading API references...">
<APIReferenceAsync {...props} />
</Suspense>
)
}

async function APIReferenceAsync({
source,
filter,
}: {
source: ExportSource<any>
filter?: SymbolFilter
}) {
...props
}: APIReferenceProps) {
if (typeof source === 'string') {
let workingDirectory: string | undefined

if ('workingDirectory' in props && props.workingDirectory) {
if (URL.canParse(props.workingDirectory)) {
const { pathname } = new URL(props.workingDirectory)
workingDirectory = pathname.slice(0, pathname.lastIndexOf('/'))
} else {
workingDirectory = props.workingDirectory
}
}

const exportedTypes = await getExportedTypes(
source,
filter,
workingDirectory
)

return exportedTypes.map((type) => (
<div
key={type.name}
css={{
display: 'flex',
flexDirection: 'column',
padding: '1.6rem 0',
borderBottom: '1px solid var(--color-separator-secondary)',
}}
>
<div
css={{
display: 'flex',
flexDirection: 'column',
gap: '0.8rem',
}}
>
<div
css={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
}}
>
<h3
id={type.name ? createSlug(type.name) : undefined}
css={{ flexShrink: 0, margin: 0 }}
>
{type.name}
</h3>

<CodeInline value={type.text} language="typescript" />

{/* {type.path && <ViewSource href={type.path} />} */}
</div>

{type.description ? (
<MDXContent value={type.description} components={mdxComponents} />
) : null}
</div>

<div css={{ display: 'flex' }}>
<TypeChildren type={type} css={{ marginTop: '2rem' }} />
</div>
</div>
))
}

const type = await source.getType(filter)

if (type === undefined) {
Expand Down Expand Up @@ -429,18 +525,3 @@ function TypeValue({
</div>
)
}

/** Displays type documentation for all types related to a collection export source. */
export function APIReference({
source,
filter,
}: {
source: ExportSource<any>
filter?: SymbolFilter
}) {
return (
<Suspense fallback="Loading API references...">
<APIReferenceAsync source={source} filter={filter} />
</Suspense>
)
}
4 changes: 3 additions & 1 deletion packages/renoun/src/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ export async function resolveType({
declaration,
projectOptions,
filter,
useClient = true,
}: {
declaration: Node
projectOptions?: ProjectOptions
filter?: SymbolFilter
useClient?: boolean
}) {
if (client) {
if (useClient && client) {
const filePath = declaration.getSourceFile().getFilePath()
const position = declaration.getPos()

Expand Down

0 comments on commit fc1e9a6

Please sign in to comment.