Files
react-test/packages/api-docs-builder/utils/createDescribeableProp.ts
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

92 lines
2.9 KiB
TypeScript

import * as doctrine from 'doctrine';
import { PropDescriptor, PropTypeDescriptor } from 'react-docgen';
export interface DescribeablePropDescriptor {
annotation: doctrine.Annotation;
defaultValue: string | null;
required: boolean;
type: PropTypeDescriptor;
}
export type CreateDescribeablePropSettings = {
/**
* Names of props that do not check if the annotations equal runtime default.
*/
propsWithoutDefaultVerification?: string[];
};
/**
* Returns `null` if the prop should be ignored.
* Throws if it is invalid.
* @param prop
* @param propName
*/
export default function createDescribeableProp(
prop: PropDescriptor,
propName: string,
settings: CreateDescribeablePropSettings = {},
): DescribeablePropDescriptor | null {
const { defaultValue, jsdocDefaultValue, description, required, type } = prop;
const { propsWithoutDefaultVerification = [] } = settings;
const renderedDefaultValue = defaultValue?.value.replace(/\r?\n/g, '');
const renderDefaultValue = Boolean(
renderedDefaultValue &&
// Ignore "large" default values that would break the table layout.
renderedDefaultValue.length <= 150,
);
if (description === undefined) {
throw new Error(`The "${propName}" prop is missing a description.`);
}
const annotation = doctrine.parse(description, {
sloppy: true,
});
if (
annotation.description.trim() === '' ||
annotation.tags.some((tag) => tag.title === 'ignore')
) {
return null;
}
if (jsdocDefaultValue !== undefined && defaultValue === undefined) {
// Assume that this prop:
// 1. Is typed by another component
// 2. Is forwarded to that component
// Then validation is handled by the other component.
// Though this does break down if the prop is used in other capacity in the implementation.
// So let's hope we don't make this mistake too often.
} else if (jsdocDefaultValue === undefined && defaultValue !== undefined && renderDefaultValue) {
const shouldHaveDefaultAnnotation =
// Discriminator for polymorphism which is not documented at the component level.
// The documentation of `component` does not know in which component it is used.
propName !== 'component';
if (shouldHaveDefaultAnnotation) {
throw new Error(
`JSDoc @default annotation not found. Add \`@default ${defaultValue.value}\` to the JSDoc of this prop.`,
);
}
} else if (
jsdocDefaultValue !== undefined &&
!propsWithoutDefaultVerification.includes(propName)
) {
// `defaultValue` can't be undefined or we would've thrown earlier.
if (jsdocDefaultValue.value !== defaultValue!.value) {
throw new Error(
`Expected JSDoc @default annotation for prop '${propName}' of "${jsdocDefaultValue.value}" to equal runtime default value of "${defaultValue?.value}"`,
);
}
}
return {
annotation,
defaultValue: renderDefaultValue ? renderedDefaultValue! : null,
required: Boolean(required),
type,
};
}