Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clear-inherited-descriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-docgen': patch
---

Use the overriding type property's description when it provides one, while preserving inherited descriptions for undocumented overrides and descriptions supplied by higher-priority handlers.
55 changes: 49 additions & 6 deletions packages/react-docgen/src/handlers/codeTypeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,40 @@ import type { TypeParameters } from '../utils/getTypeParameters.js';
import resolveToValue from '../utils/resolveToValue.js';
import setPropDescription from '../utils/setPropDescription.js';
import type { NodePath } from '@babel/traverse';
import type { FlowType } from '@babel/types';
import type {
FlowType,
ObjectTypeProperty,
TSPropertySignature,
} from '@babel/types';
import { getDocblock } from '../utils/docblock.js';
import type { ComponentNode } from '../resolver/index.js';
import type { Handler } from './index.js';

function setTypePropDescription(
documentation: Documentation,
path: NodePath<ObjectTypeProperty | TSPropertySignature>,
describedProps: Set<string>,
): void {
const propName = getPropertyName(path);

if (!propName) return;
const descriptor = documentation.getPropDescriptor(propName);

// Only replace descriptions produced by this handler, never higher-priority handlers.
if (describedProps.has(propName) && getDocblock(path)) {
descriptor.description = '';
}
const hadDescription = Boolean(descriptor.description);

setPropDescription(documentation, path);
if (!hadDescription) describedProps.add(propName);
}

function setPropDescriptor(
documentation: Documentation,
path: NodePath,
typeParams: TypeParameters | null,
describedProps: Set<string>,
): void {
if (path.isObjectTypeSpreadProperty()) {
const argument = unwrapUtilityType(
Expand All @@ -29,7 +55,12 @@ function setPropDescriptor(
documentation,
argument,
(propertyPath, innerTypeParams) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
setPropDescriptor(
documentation,
propertyPath,
innerTypeParams,
describedProps,
);
},
typeParams,
);
Expand All @@ -51,7 +82,12 @@ function setPropDescriptor(
documentation,
right,
(propertyPath, innerTypeParams) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
setPropDescriptor(
documentation,
propertyPath,
innerTypeParams,
describedProps,
);
},
typeParams,
);
Expand All @@ -72,7 +108,7 @@ function setPropDescriptor(
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
setTypePropDescription(documentation, path, describedProps);
} else if (path.isTSPropertySignature()) {
const typeAnnotation = path.get('typeAnnotation');

Expand All @@ -93,7 +129,7 @@ function setPropDescriptor(
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
setTypePropDescription(documentation, path, describedProps);
}
}

Expand All @@ -112,12 +148,19 @@ const codeTypeHandler: Handler = function (
return;
}

const describedProps = new Set<string>();

for (const typePath of typePaths) {
applyToTypeProperties(
documentation,
typePath,
(propertyPath, typeParams) => {
setPropDescriptor(documentation, propertyPath, typeParams);
setPropDescriptor(
documentation,
propertyPath,
typeParams,
describedProps,
);
},
null,
);
Expand Down
47 changes: 47 additions & 0 deletions packages/react-docgen/tests/integration/integration-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,53 @@ import { parse, makeFsImporter } from '../../src/main';
import { fileURLToPath } from 'url';

describe('integration', () => {
test.each([
['/** child */', 'child'],
['', 'parent'],
])(
'uses an overriding property description when present: %s',
(comment, expected) => {
const result = parse(
`interface Parent { /** parent */ value: any; }
interface Props extends Parent { ${comment} value: string; }
export default function Example(props: Props) { return <div />; }`,
{ babelOptions: { filename: 'inherited.tsx', babelrc: false } },
);

expect(result[0]?.props?.value).toMatchObject({
description: expected,
tsType: { name: 'string' },
});
},
);

test('preserves a propTypes description over inherited type comments', () => {
const result = parse(
`import PropTypes from 'prop-types';
interface Parent { /** parent */ value: any; }
interface Props extends Parent { /** child */ value: string; }
export default function Example(props: Props) { return <div />; }
Example.propTypes = { /** runtime */ value: PropTypes.string };`,
{ babelOptions: { filename: 'inherited.tsx', babelrc: false } },
);

expect(result[0]?.props?.value?.description).toBe('runtime');
});

test('uses the overriding description after a Flow type spread', () => {
const result = parse(
`type Parent = { /** parent */ value: any };
type Props = { ...Parent, /** child */ value: string };
export default function Example(props: Props) { return <div />; }`,
{ babelOptions: { filename: 'inherited.js', babelrc: false } },
);

expect(result[0]?.props?.value).toMatchObject({
description: 'child',
flowType: { name: 'string' },
});
});

describe('fixtures', () => {
const fixturePath = join(
dirname(fileURLToPath(import.meta.url)),
Expand Down