[WEB-5459] feat(codemods): add function declaration transformer with tests (#8137)

- Add jscodeshift-based codemod to convert arrow function components to function declarations
- Support React.FC, observer-wrapped, and forwardRef components
- Include comprehensive test suite covering edge cases
- Add npm script to run transformer across codebase
- Target only .tsx files in source directories, excluding node_modules and declaration files

* [WEB-5459] chore: updates after running codemod

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Aaron 2025-11-20 19:09:40 +07:00 committed by GitHub
parent 90866fb925
commit 83fdebf64d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1771 changed files with 17003 additions and 13856 deletions

View file

@ -18,7 +18,7 @@ type Props = {
};
// TODO: Remove label once i18n is done
export const AccessField = (props: Props) => {
export function AccessField(props: Props) {
const { onChange, value, accessSpecifiers, isMobile = false } = props;
const { t } = useTranslation();
@ -50,4 +50,4 @@ export const AccessField = (props: Props) => {
})}
</div>
);
};
}

View file

@ -21,7 +21,7 @@ type TActivityBlockComponent = {
customUserName?: string;
};
export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
export function ActivityBlockComponent(props: TActivityBlockComponent) {
const { icon, activity, ends, children, customUserName } = props;
// hooks
const { isMobile } = usePlatformOS();
@ -53,4 +53,4 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
</div>
</div>
);
};
}

View file

@ -13,7 +13,7 @@ type TActivityItem = {
ends?: "top" | "bottom" | undefined;
};
export const ActivityItem: FC<TActivityItem> = observer((props) => {
export const ActivityItem = observer(function ActivityItem(props: TActivityItem) {
const { activity, showProject = true, ends } = props;
if (!activity) return null;

View file

@ -12,7 +12,7 @@ type TUser = {
customUserName?: string;
};
export const User: FC<TUser> = observer((props) => {
export const User = observer(function User(props: TUser) {
const { activity, customUserName } = props;
// store hooks
const { getUserDetails } = useMember();

View file

@ -11,7 +11,7 @@ type Props = {
values: string[];
};
export const AppliedDateFilters: React.FC<Props> = observer((props) => {
export const AppliedDateFilters = observer(function AppliedDateFilters(props: Props) {
const { editable, handleRemove, values } = props;
const getDateLabel = (value: string): string => {

View file

@ -15,7 +15,7 @@ type Props = {
editable: boolean | undefined;
};
export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
export const AppliedMembersFilters = observer(function AppliedMembersFilters(props: Props) {
const { handleRemove, values, editable } = props;
// store hooks
const {

View file

@ -15,19 +15,25 @@ type Props = {
isLast?: boolean;
};
const IconWrapper = React.memo(({ icon }: { icon: React.ReactNode }) => (
<div className="flex size-4 items-center justify-center overflow-hidden !text-[1rem]">{icon}</div>
));
const IconWrapper = React.memo(function IconWrapper({ icon }: { icon: React.ReactNode }) {
return <div className="flex size-4 items-center justify-center overflow-hidden !text-[1rem]">{icon}</div>;
});
IconWrapper.displayName = "IconWrapper";
const LabelWrapper = React.memo(({ label }: { label: ReactNode }) => (
<div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>
));
const LabelWrapper = React.memo(function LabelWrapper({ label }: { label: ReactNode }) {
return <div className="relative line-clamp-1 block max-w-[150px] overflow-hidden truncate">{label}</div>;
});
LabelWrapper.displayName = "LabelWrapper";
const BreadcrumbContent = React.memo(({ icon, label }: { icon?: React.ReactNode; label?: ReactNode }) => {
const BreadcrumbContent = React.memo(function BreadcrumbContent({
icon,
label,
}: {
icon?: React.ReactNode;
label?: ReactNode;
}) {
if (!icon && !label) return null;
return (
@ -40,13 +46,16 @@ const BreadcrumbContent = React.memo(({ icon, label }: { icon?: React.ReactNode;
BreadcrumbContent.displayName = "BreadcrumbContent";
const ItemWrapper = React.memo(({ children, ...props }: React.ComponentProps<typeof Breadcrumbs.ItemWrapper>) => (
<Breadcrumbs.ItemWrapper {...props}>{children}</Breadcrumbs.ItemWrapper>
));
const ItemWrapper = React.memo(function ItemWrapper({
children,
...props
}: React.ComponentProps<typeof Breadcrumbs.ItemWrapper>) {
return <Breadcrumbs.ItemWrapper {...props}>{children}</Breadcrumbs.ItemWrapper>;
});
ItemWrapper.displayName = "ItemWrapper";
export const BreadcrumbLink: FC<Props> = observer((props) => {
export const BreadcrumbLink = observer(function BreadcrumbLink(props: Props) {
const { href, label, icon, disableTooltip = false, isLast = false } = props;
const { isMobile } = usePlatformOS();

View file

@ -9,7 +9,7 @@ type TCountChip = {
className?: string;
};
export const CountChip: FC<TCountChip> = (props) => {
export function CountChip(props: TCountChip) {
const { count, className = "" } = props;
return (
@ -22,4 +22,4 @@ export const CountChip: FC<TCountChip> = (props) => {
{count}
</div>
);
};
}

View file

@ -17,32 +17,27 @@ type Props = {
disabled?: boolean;
};
export const EmptyState: React.FC<Props> = ({
title,
description,
image,
primaryButton,
secondaryButton,
disabled = false,
}) => (
<div className={`flex h-full w-full items-center justify-center`}>
<div className="flex w-full flex-col items-center text-center">
<img src={image} className="w-52 sm:w-60 object-contain" alt={primaryButton?.text || "button image"} />
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
export function EmptyState({ title, description, image, primaryButton, secondaryButton, disabled = false }: Props) {
return (
<div className={`flex h-full w-full items-center justify-center`}>
<div className="flex w-full flex-col items-center text-center">
<img src={image} className="w-52 sm:w-60 object-contain" alt={primaryButton?.text || "button image"} />
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
</div>
</div>
</div>
</div>
);
);
}

View file

@ -13,7 +13,7 @@ type Props = {
searchQuery: string;
};
export const FilterCreatedDate: React.FC<Props> = observer((props) => {
export const FilterCreatedDate = observer(function FilterCreatedDate(props: Props) {
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);

View file

@ -20,7 +20,7 @@ type Props = {
searchQuery: string;
};
export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
export const FilterCreatedBy = observer(function FilterCreatedBy(props: Props) {
const { appliedFilters, handleUpdate, memberIds, searchQuery } = props;
// states
const [itemsToRender, setItemsToRender] = useState(5);

View file

@ -5,7 +5,7 @@ import { Lightbulb } from "lucide-react";
// images
import latestFeatures from "@/app/assets/onboarding/onboarding-pages.webp?url";
export const LatestFeatureBlock = () => {
export function LatestFeatureBlock() {
const { resolvedTheme } = useTheme();
return (
@ -36,4 +36,4 @@ export const LatestFeatureBlock = () => {
</div>
</>
);
};
}

View file

@ -3,7 +3,7 @@ import { useTheme } from "next-themes";
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
export const LogoSpinner = () => {
export function LogoSpinner() {
const { resolvedTheme } = useTheme();
const logoSrc = resolvedTheme === "dark" ? LogoSpinnerDark : LogoSpinnerLight;
@ -13,4 +13,4 @@ export const LogoSpinner = () => {
<img src={logoSrc} alt="logo" className="h-6 w-auto sm:h-11 object-contain" />
</div>
);
};
}

View file

@ -22,14 +22,7 @@ type Props = {
disabled?: boolean;
};
export const NewEmptyState: React.FC<Props> = ({
title,
description,
image,
primaryButton,
disabled = false,
comicBox,
}) => {
export function NewEmptyState({ title, description, image, primaryButton, disabled = false, comicBox }: Props) {
const [isHovered, setIsHovered] = useState(false);
const handleMouseEnter = () => {
@ -109,4 +102,4 @@ export const NewEmptyState: React.FC<Props> = ({
</div>
</div>
);
};
}

View file

@ -2,14 +2,16 @@ import { ArchiveIcon, Earth, Lock } from "lucide-react";
import { EPageAccess } from "@plane/constants";
import type { TPage } from "@plane/types";
export const PageAccessIcon = (page: TPage) => (
<div>
{page.archived_at ? (
<ArchiveIcon className="h-2.5 w-2.5 text-custom-text-300" />
) : page.access === EPageAccess.PUBLIC ? (
<Earth className="h-2.5 w-2.5 text-custom-text-300" />
) : (
<Lock className="h-2.5 w-2.5 text-custom-text-300" />
)}
</div>
);
export function PageAccessIcon(page: TPage) {
return (
<div>
{page.archived_at ? (
<ArchiveIcon className="h-2.5 w-2.5 text-custom-text-300" />
) : page.access === EPageAccess.PUBLIC ? (
<Earth className="h-2.5 w-2.5 text-custom-text-300" />
) : (
<Lock className="h-2.5 w-2.5 text-custom-text-300" />
)}
</div>
);
}

View file

@ -9,8 +9,8 @@ type TProIcon = {
className?: string;
};
export const ProIcon: FC<TProIcon> = (props) => {
export function ProIcon(props: TProIcon) {
const { className } = props;
return <Crown className={cn("inline-block h-3.5 w-3.5 text-amber-400", className)} />;
};
}

View file

@ -12,13 +12,7 @@ type TSwitcherIconProps = {
type?: "lucide" | "material";
};
export const SwitcherIcon: FC<TSwitcherIconProps> = ({
logo_props,
logo_url,
LabelIcon,
size = 12,
type = "lucide",
}) => {
export function SwitcherIcon({ logo_props, logo_url, LabelIcon, size = 12, type = "lucide" }: TSwitcherIconProps) {
if (logo_props?.in_use) {
return <Logo logo={logo_props} size={size} type={type} />;
}
@ -34,7 +28,7 @@ export const SwitcherIcon: FC<TSwitcherIconProps> = ({
);
}
return <LabelIcon height={size} width={size} />;
};
}
type TSwitcherLabelProps = {
logo_props?: TLogoProps;
@ -44,7 +38,7 @@ type TSwitcherLabelProps = {
type?: "lucide" | "material";
};
export const SwitcherLabel: FC<TSwitcherLabelProps> = (props) => {
export function SwitcherLabel(props: TSwitcherLabelProps) {
const { logo_props, name, LabelIcon, logo_url, type = "lucide" } = props;
return (
<div className="flex items-center gap-1 text-custom-text-200">
@ -52,4 +46,4 @@ export const SwitcherLabel: FC<TSwitcherLabelProps> = (props) => {
{truncateText(name ?? "", 40)}
</div>
);
};
}