From 861114f3f0b6ffa582fa25b90cea48800478d2af Mon Sep 17 00:00:00 2001 From: Andrew Jiang Date: Wed, 9 Oct 2024 11:24:56 -0400 Subject: [PATCH] delete more code --- packages/fdr-sdk/src/api-definition/unwrap.ts | 66 +++++-- .../DiscriminatedUnionVariant.tsx | 1 - .../type-definition/EnumDefinitionDetails.tsx | 10 +- .../InternalTypeDefinition.tsx | 103 +--------- .../InternalTypeDefinitionError.module.scss | 12 -- .../InternalTypeDefinitionError.tsx | 185 ------------------ .../createCollapsibleContent.tsx | 97 +++++++++ .../InternalTypeReferenceDefinitions.tsx | 157 ++++++--------- .../ListTypeContextProvider.tsx | 7 +- 9 files changed, 222 insertions(+), 416 deletions(-) delete mode 100644 packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.module.scss delete mode 100644 packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.tsx create mode 100644 packages/ui/app/src/api-reference/types/type-definition/createCollapsibleContent.tsx diff --git a/packages/fdr-sdk/src/api-definition/unwrap.ts b/packages/fdr-sdk/src/api-definition/unwrap.ts index 234d77bc62..54fcbde55d 100644 --- a/packages/fdr-sdk/src/api-definition/unwrap.ts +++ b/packages/fdr-sdk/src/api-definition/unwrap.ts @@ -25,6 +25,11 @@ type InternalDefaultValue = | { type: "unknown"; value: unknown } | { type: "typeReferenceId"; value: Latest.TypeReferenceIdDefault }; +/** + * Cache for unwrapped references. Perform the unwrapping only once. + */ +const UnwrapReferenceCache = new WeakMap(); + /** * A TypeShape or TypeReference might be an alias or reference to another type. * This function unwraps the reference, including any optional wrappers, to get the actual shape. @@ -55,39 +60,45 @@ export function unwrapReference( return undefined; } + const cached = UnwrapReferenceCache.get(typeRef); + if (cached != null) { + return cached; + } + let isOptional = false; const defaults: InternalDefaultValue[] = []; const descriptions: FernDocs.MarkdownText[] = []; const availabilities: Latest.Availability[] = []; + let internalTypeRef: TypeShapeOrReference | undefined = typeRef; let loop = 0; - while (typeRef != null) { + while (internalTypeRef != null) { if (loop > LOOP_TOLERANCE) { // eslint-disable-next-line no-console console.error("Infinite loop detected while unwrapping type reference. Falling back to unknown type."); - typeRef = undefined; + internalTypeRef = undefined; break; } - if (typeRef.type === "optional") { + if (internalTypeRef.type === "optional") { isOptional = true; - if (typeRef.default != null) { - defaults.push({ type: "unknown", value: typeRef.default }); + if (internalTypeRef.default != null) { + defaults.push({ type: "unknown", value: internalTypeRef.default }); } - typeRef = typeRef.shape; - } else if (typeRef.type === "alias") { - typeRef = typeRef.value; - } else if (typeRef.type === "id") { - if (typeRef.default != null) { - defaults.push({ type: "typeReferenceId", value: typeRef.default }); + internalTypeRef = internalTypeRef.shape; + } else if (internalTypeRef.type === "alias") { + internalTypeRef = internalTypeRef.value; + } else if (internalTypeRef.type === "id") { + if (internalTypeRef.default != null) { + defaults.push({ type: "typeReferenceId", value: internalTypeRef.default }); } - const typeDef: Latest.TypeDefinition | undefined = types[typeRef.id]; + const typeDef: Latest.TypeDefinition | undefined = types[internalTypeRef.id]; if (typeDef != null) { if (typeDef.availability) { availabilities.push(typeDef.availability); } - typeRef = typeDef.shape; + internalTypeRef = typeDef.shape; if (typeDef.description != null) { descriptions.push(typeDef.description); } @@ -99,19 +110,23 @@ export function unwrapReference( loop++; } - if (typeRef == null) { + if (internalTypeRef == null) { // Note: this should be a fatal error, but we're handling it gracefully for now // eslint-disable-next-line no-console console.error("Type reference is invalid. Falling back to unknown type."); } - return { - shape: typeRef ?? { type: "unknown", displayName: undefined }, + const toRet = { + shape: internalTypeRef ?? { type: "unknown", displayName: undefined }, availability: coalesceAvailability(availabilities), isOptional, - default: selectDefaultValue(typeRef, defaults), + default: selectDefaultValue(internalTypeRef, defaults), descriptions, }; + + UnwrapReferenceCache.set(typeRef, toRet); + + return toRet; } function selectDefaultValue( @@ -155,6 +170,8 @@ function selectDefaultValue( } } +const UnwrapObjectTypeCache = new WeakMap(); + /** * Dereferences extended objects and returns all properties of the object. * If an object extends another object, the properties of the extended object will be sorted alphabetically. @@ -168,6 +185,11 @@ export function unwrapObjectType( object: Latest.ObjectType, types: Record, ): UnwrappedObjectType { + const cached = UnwrapObjectTypeCache.get(object); + if (cached != null) { + return cached; + } + const directProperties = object.properties; const descriptions: FernDocs.MarkdownText[] = []; const extendedProperties = object.extends.flatMap((typeId): Latest.ObjectProperty[] => { @@ -230,7 +252,9 @@ export function unwrapObjectType( (property) => unwrapReference(property.valueShape, types)?.isOptional, (property) => AvailabilityOrder.indexOf(property.availability ?? Latest.Availability.Stable), ); - return { properties, descriptions }; + const toRet = { properties, descriptions }; + UnwrapObjectTypeCache.set(object, toRet); + return toRet; } const propertyKeys = new Set(object.properties.map((property) => property.key)); const filteredExtendedProperties = extendedProperties.filter( @@ -245,7 +269,9 @@ export function unwrapObjectType( (property) => AvailabilityOrder.indexOf(property.availability ?? Latest.Availability.Stable), (property) => property.key, ); - return { properties, descriptions }; + const toRet = { properties, descriptions }; + UnwrapObjectTypeCache.set(object, toRet); + return toRet; } /** @@ -256,7 +282,7 @@ export function unwrapDiscriminatedUnionVariant( variant: Latest.DiscriminatedUnionVariant, types: Record, ): UnwrappedObjectType { - const { properties, descriptions } = unwrapObjectType(variant, types); + const { properties, descriptions } = unwrapObjectType(variant, types); // this is already cached return { properties: [ { diff --git a/packages/ui/app/src/api-reference/types/discriminated-union/DiscriminatedUnionVariant.tsx b/packages/ui/app/src/api-reference/types/discriminated-union/DiscriminatedUnionVariant.tsx index ecaccd6c49..9c787943ba 100644 --- a/packages/ui/app/src/api-reference/types/discriminated-union/DiscriminatedUnionVariant.tsx +++ b/packages/ui/app/src/api-reference/types/discriminated-union/DiscriminatedUnionVariant.tsx @@ -33,7 +33,6 @@ export const DiscriminatedUnionVariant: React.FC { const { isRootTypeDefinition } = useTypeDefinitionContext(); - // TODO: render descriptions const [shape, descriptions] = useMemo((): [ApiDefinition.TypeShape.Object_, FernDocs.MarkdownText[]] => { const unwrapped = ApiDefinition.unwrapDiscriminatedUnionVariant({ discriminant }, unionVariant, types); return [ diff --git a/packages/ui/app/src/api-reference/types/type-definition/EnumDefinitionDetails.tsx b/packages/ui/app/src/api-reference/types/type-definition/EnumDefinitionDetails.tsx index bd2b8ad94c..f2f78bc0e1 100644 --- a/packages/ui/app/src/api-reference/types/type-definition/EnumDefinitionDetails.tsx +++ b/packages/ui/app/src/api-reference/types/type-definition/EnumDefinitionDetails.tsx @@ -1,14 +1,12 @@ import { Empty } from "@fern-ui/components"; import { ReactElement, useEffect, useState } from "react"; -export declare namespace TypeDefinitionDetails { - export interface Props { - elements: ReactElement[]; - searchInput: string; - } +export interface EnumDefinitionDetailsProps { + elements: ReactElement[]; + searchInput: string; } -export const EnumDefinitionDetails = ({ elements, searchInput }: TypeDefinitionDetails.Props): ReactElement => { +export const EnumDefinitionDetails = ({ elements, searchInput }: EnumDefinitionDetailsProps): ReactElement => { const [filteredElements, setFilteredElements] = useState([]); useEffect(() => { diff --git a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinition.tsx b/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinition.tsx index f21772980b..c2ed8cca98 100644 --- a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinition.tsx +++ b/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinition.tsx @@ -1,12 +1,10 @@ import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; -import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils"; import { FernTooltipProvider } from "@fern-ui/components"; import { useBooleanState, useIsHovering } from "@fern-ui/react-commons"; import cn from "clsx"; -import { ReactElement, memo, useCallback, useMemo } from "react"; +import { memo, useCallback, useMemo } from "react"; import { useRouteListener } from "../../../atoms"; -import { Chip } from "../../../components/Chip"; import { FernErrorBoundary } from "../../../components/FernErrorBoundary"; import { getAnchorId } from "../../../util/anchor"; import { @@ -14,12 +12,10 @@ import { TypeDefinitionContextValue, useTypeDefinitionContext, } from "../context/TypeDefinitionContext"; -import { DiscriminatedUnionVariant } from "../discriminated-union/DiscriminatedUnionVariant"; -import { ObjectProperty } from "../object/ObjectProperty"; -import { UndiscriminatedUnionVariant } from "../undiscriminated-union/UndiscriminatedUnionVariant"; import { EnumTypeDefinition } from "./EnumTypeDefinition"; import { FernCollapseWithButton } from "./FernCollapseWithButton"; import { TypeDefinitionDetails } from "./TypeDefinitionDetails"; +import { createCollapsibleContent } from "./createCollapsibleContent"; export declare namespace InternalTypeDefinition { export interface Props { @@ -31,13 +27,6 @@ export declare namespace InternalTypeDefinition { } } -interface CollapsibleContent { - elements: ReactElement[]; - elementNameSingular: string; - elementNamePlural: string; - separatorText?: string; -} - export const InternalTypeDefinition = memo(function InternalTypeDefinition({ shape, isCollapsible, @@ -45,84 +34,10 @@ export const InternalTypeDefinition = memo(functio slug, types, }) { - const collapsableContent = useMemo(() => { - const unwrapped = ApiDefinition.unwrapReference(shape, types); - return visitDiscriminatedUnion(unwrapped.shape)._visit({ - object: (object) => { - const { properties } = ApiDefinition.unwrapObjectType(object, types); - return { - elements: properties.map((property) => ( - - )), - elementNameSingular: "property", - elementNamePlural: "properties", - }; - }, - undiscriminatedUnion: (union) => ({ - elements: union.variants.map((variant, variantIdx) => ( - - )), - elementNameSingular: "variant", - elementNamePlural: "variants", - separatorText: "OR", - }), - discriminatedUnion: (union) => ({ - elements: union.variants.map((variant) => ( - - )), - elementNameSingular: "variant", - elementNamePlural: "variants", - separatorText: "OR", - }), - enum: (enum_) => ({ - elements: enum_.values.map((enumValue) => ( - - // - )), - elementNameSingular: "enum value", - elementNamePlural: "enum values", - }), - literal: (literal) => ({ - elements: [ - visitDiscriminatedUnion(literal.value, "type")._visit({ - stringLiteral: (value) => , - booleanLiteral: (value) => , - _other: () => {""}, - }), - ], - elementNameSingular: "literal", - elementNamePlural: "literals", - }), - unknown: () => undefined, - _other: () => undefined, - primitive: () => undefined, - list: () => undefined, - set: () => undefined, - map: () => undefined, - }); - }, [shape, types, anchorIdParts, slug]); + const collapsableContent = useMemo( + () => createCollapsibleContent(shape, types, anchorIdParts, slug), + [shape, types, anchorIdParts, slug], + ); const anchorIdSoFar = getAnchorId(anchorIdParts); const { value: isCollapsed, toggleValue: toggleIsCollapsed, setValue: setCollapsed } = useBooleanState(true); @@ -151,9 +66,9 @@ export const InternalTypeDefinition = memo(functio if (!isCollapsible) { // TODO: (rohin) Refactor this - if (collapsableContent.elementNameSingular === "literal") { - return null; - } + // if (collapsableContent.elementNameSingular === "literal") { + // return null; + // } return ( diff --git a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.module.scss b/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.module.scss deleted file mode 100644 index 6b5f6d9680..0000000000 --- a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.showPropertiesButton { - display: flex; - flex-direction: column; - min-width: 0; - - &::before { - content: attr(data-show-text); - display: block; - height: 0; - visibility: hidden; - } -} diff --git a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.tsx b/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.tsx deleted file mode 100644 index f738a23cb1..0000000000 --- a/packages/ui/app/src/api-reference/types/type-definition/InternalTypeDefinitionError.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; -import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; -import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils"; -import { useBooleanState, useIsHovering } from "@fern-ui/react-commons"; -import React, { ReactElement, useCallback, useMemo } from "react"; -import { useRouteListener } from "../../../atoms"; -import { getAnchorId } from "../../../util/anchor"; -import { - TypeDefinitionContext, - TypeDefinitionContextValue, - useTypeDefinitionContext, -} from "../context/TypeDefinitionContext"; -import { DiscriminatedUnionVariant } from "../discriminated-union/DiscriminatedUnionVariant"; -import { EnumValue } from "../enum/EnumValue"; -import { ObjectProperty } from "../object/ObjectProperty"; -import { UndiscriminatedUnionVariant } from "../undiscriminated-union/UndiscriminatedUnionVariant"; -import { FernCollapseWithButton } from "./FernCollapseWithButton"; -import { TypeDefinitionDetails } from "./TypeDefinitionDetails"; - -export declare namespace InternalTypeDefinitionError { - export interface Props { - isCollapsible: boolean; - anchorIdParts: readonly string[]; - slug: FernNavigation.Slug; - shape: ApiDefinition.TypeShapeOrReference; - types: Record; - } -} - -interface CollapsibleContent { - elements: ReactElement[]; - elementNameSingular: string; - elementNamePlural: string; - separatorText?: string; -} - -export const InternalTypeDefinitionError: React.FC = ({ - shape, - isCollapsible, - anchorIdParts, - slug, - types, -}) => { - const collapsableContent = useMemo(() => { - const unwrapped = ApiDefinition.unwrapReference(shape, types); - return visitDiscriminatedUnion(unwrapped.shape)._visit({ - object: (object) => { - const { properties } = ApiDefinition.unwrapObjectType(object, types); - return { - elements: properties.map((property) => ( - - )), - elementNameSingular: "property", - elementNamePlural: "properties", - }; - }, - undiscriminatedUnion: (union) => ({ - elements: union.variants.map((variant, variantIdx) => ( - - )), - elementNameSingular: "variant", - elementNamePlural: "variants", - separatorText: "OR", - }), - discriminatedUnion: (union) => ({ - elements: union.variants.map((variant) => ( - - )), - elementNameSingular: "variant", - elementNamePlural: "variants", - separatorText: "OR", - }), - enum: (enum_) => ({ - elements: enum_.values.map((enumValue) => ), - elementNameSingular: "enum value", - elementNamePlural: "enum values", - }), - literal: (literal) => ({ - elements: [ - `${value.value}`, - booleanLiteral: (value) => `${value.value ? "true" : "false"}`, - _other: () => "", - }), - description: undefined, - availability: undefined, - }} - />, - ], - elementNameSingular: "literal", - elementNamePlural: "literals", - }), - primitive: () => undefined, - list: () => undefined, - set: () => undefined, - map: () => undefined, - unknown: () => undefined, - }); - }, [shape, types, anchorIdParts, slug]); - - const anchorIdSoFar = getAnchorId(anchorIdParts); - const { value: isCollapsed, toggleValue: toggleIsCollapsed, setValue: setCollapsed } = useBooleanState(true); - - useRouteListener(slug, (anchor) => { - const isActive = anchor?.startsWith(anchorIdSoFar + ".") ?? false; - if (isActive) { - setCollapsed(false); - } - }); - - const { isHovering, ...containerCallbacks } = useIsHovering(); - - const contextValue = useTypeDefinitionContext(); - const collapsibleContentContextValue = useCallback( - (): TypeDefinitionContextValue => ({ - ...contextValue, - isRootTypeDefinition: false, - }), - [contextValue], - ); - - if (collapsableContent == null || collapsableContent.elements.length === 0) { - return null; - } - - if (!isCollapsible) { - return ( - - ); - } - - const showText = - collapsableContent.elements.length === 1 - ? `Show ${collapsableContent.elementNameSingular}` - : `Show ${collapsableContent.elements.length} ${collapsableContent.elementNamePlural}`; - const hideText = - collapsableContent.elements.length === 1 - ? `Hide ${collapsableContent.elementNameSingular}` - : `Hide ${collapsableContent.elements.length} ${collapsableContent.elementNamePlural}`; - - return ( - - - - - - ); -}; diff --git a/packages/ui/app/src/api-reference/types/type-definition/createCollapsibleContent.tsx b/packages/ui/app/src/api-reference/types/type-definition/createCollapsibleContent.tsx new file mode 100644 index 0000000000..284ed649fb --- /dev/null +++ b/packages/ui/app/src/api-reference/types/type-definition/createCollapsibleContent.tsx @@ -0,0 +1,97 @@ +import { + TypeDefinition, + TypeId, + TypeShapeOrReference, + unwrapObjectType, + unwrapReference, +} from "@fern-api/fdr-sdk/api-definition"; +import { Slug } from "@fern-api/fdr-sdk/navigation"; +import { ReactElement } from "react"; +import { Chip } from "../../../components/Chip"; +import { DiscriminatedUnionVariant } from "../discriminated-union/DiscriminatedUnionVariant"; +import { ObjectProperty } from "../object/ObjectProperty"; +import { UndiscriminatedUnionVariant } from "../undiscriminated-union/UndiscriminatedUnionVariant"; + +interface CollapsibleContent { + elements: ReactElement[]; + elementNameSingular: string; + elementNamePlural: string; + separatorText?: string; +} + +export function createCollapsibleContent( + shape: TypeShapeOrReference, + types: Record, + anchorIdParts: readonly string[], + slug: Slug, +): CollapsibleContent | undefined { + const unwrapped = unwrapReference(shape, types); + + switch (unwrapped.shape.type) { + case "discriminatedUnion": { + const union = unwrapped.shape; + return { + elements: union.variants.map((variant) => ( + + )), + elementNameSingular: "variant", + elementNamePlural: "variants", + separatorText: "OR", + }; + } + case "enum": { + return { + elements: unwrapped.shape.values.map((enumValue) => ( + + // + )), + elementNameSingular: "enum value", + elementNamePlural: "enum values", + }; + } + case "object": { + const { properties } = unwrapObjectType(unwrapped.shape, types); + return { + elements: properties.map((property) => ( + + )), + elementNameSingular: "property", + elementNamePlural: "properties", + }; + } + case "undiscriminatedUnion": { + return { + elements: unwrapped.shape.variants.map((variant, variantIdx) => ( + + )), + elementNameSingular: "variant", + elementNamePlural: "variants", + separatorText: "OR", + }; + } + default: + return undefined; + } +} diff --git a/packages/ui/app/src/api-reference/types/type-reference/InternalTypeReferenceDefinitions.tsx b/packages/ui/app/src/api-reference/types/type-reference/InternalTypeReferenceDefinitions.tsx index e747d57054..ccc943b44e 100644 --- a/packages/ui/app/src/api-reference/types/type-reference/InternalTypeReferenceDefinitions.tsx +++ b/packages/ui/app/src/api-reference/types/type-reference/InternalTypeReferenceDefinitions.tsx @@ -1,9 +1,9 @@ import * as ApiDefinition from "@fern-api/fdr-sdk/api-definition"; import * as FernNavigation from "@fern-api/fdr-sdk/navigation"; import { visitDiscriminatedUnion } from "@fern-api/ui-core-utils"; -import React, { ReactElement } from "react"; +import React from "react"; +import { UnreachableCaseError } from "ts-essentials"; import { InternalTypeDefinition } from "../type-definition/InternalTypeDefinition"; -import { InternalTypeDefinitionError } from "../type-definition/InternalTypeDefinitionError"; import { ListTypeContextProvider } from "./ListTypeContextProvider"; import { MapTypeContextProvider } from "./MapTypeContextProvider"; @@ -70,105 +70,78 @@ export const InternalTypeReferenceDefinitions: React.FC { - const InternalShapeRenderer = applyErrorStyles ? InternalTypeDefinitionError : InternalTypeDefinition; const unwrapped = ApiDefinition.unwrapReference(shape, types); - return visitDiscriminatedUnion(unwrapped.shape)._visit({ - object: (object) => ( - - ), - enum: (enum_) => ( - - ), - undiscriminatedUnion: (undiscriminatedUnion) => ( - - ), - discriminatedUnion: (discriminatedUnion) => ( - - ), - - list: (list) => ( - - - - ), - set: (set) => ( - - - - ), - map: (map) => ( - - - - - ), - primitive: () => null, - literal: (literal) => ( - - ), - unknown: () => null, - _other: () => null, - }); + ); + } + case "list": + case "set": { + return ( + + + + ); + } + case "map": { + return ( + + + + + ); + } + case "literal": + case "unknown": + return null; + default: + throw new UnreachableCaseError(unwrapped.shape); + } }; diff --git a/packages/ui/app/src/api-reference/types/type-reference/ListTypeContextProvider.tsx b/packages/ui/app/src/api-reference/types/type-reference/ListTypeContextProvider.tsx index 7f506c5d5e..62ed625b90 100644 --- a/packages/ui/app/src/api-reference/types/type-reference/ListTypeContextProvider.tsx +++ b/packages/ui/app/src/api-reference/types/type-reference/ListTypeContextProvider.tsx @@ -10,12 +10,7 @@ export const ListTypeContextProvider: React.FC = ({ children const newContextValue = useCallback( (): TypeDefinitionContextValue => ({ ...contextValue, - jsonPropertyPath: [ - ...contextValue.jsonPropertyPath, - { - type: "listItem", - }, - ], + jsonPropertyPath: [...contextValue.jsonPropertyPath, { type: "listItem" }], }), [contextValue], );