Remove the {...props} pattern and props coupling, and create an eslint rule for that (#1733)
* Remove the {...props} pattern and props coupling, and create an eslint rule for that
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
* Add another test to the new rule
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
---------
Co-authored-by: v1b3m <vibenjamin6@gmail.com>
Co-authored-by: Thiago Nascimbeni <tnascimbeni@gmail.com>
This commit is contained in:
@ -85,6 +85,32 @@ const EmailField = ({ value }: OwnProps) => (
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### No Single Variable Prop Spreading in JSX Elements
|
||||||
|
|
||||||
|
We discourage the use of single variable prop spreading in JSX elements, e.g., `{...props}`. This practice often leads to less readable and maintainable code as it's unclear what props are being passed down to the component.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
/* ❌ - Bad, spreads a single variable prop into the underlying component
|
||||||
|
*/
|
||||||
|
const MyComponent = (props: OwnProps) => {
|
||||||
|
return <OtherComponent {...props} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
/* ✅ - Good, Explicitly lists all props
|
||||||
|
* - Enhances readability and maintainability
|
||||||
|
*/
|
||||||
|
const MyComponent = ({ prop1, prop2, prop3 }: OwnProps) => {
|
||||||
|
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale:
|
||||||
|
- It's clearer to see at a glance which props are being passed down, making the code easier to understand and maintain.
|
||||||
|
- It helps to prevent tight coupling between components via their props.
|
||||||
|
- It's easier to catch misspelled or unused props with linting tools when props are listed explicitly
|
||||||
|
|
||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
### Use nullish-coalescing operator `??`
|
### Use nullish-coalescing operator `??`
|
||||||
|
|||||||
@ -62,6 +62,7 @@ module.exports = {
|
|||||||
'simple-import-sort/exports': 'error',
|
'simple-import-sort/exports': 'error',
|
||||||
'twenty/effect-components': 'error',
|
'twenty/effect-components': 'error',
|
||||||
'twenty/no-hardcoded-colors': 'error',
|
'twenty/no-hardcoded-colors': 'error',
|
||||||
|
'twenty/no-spread-props': 'error',
|
||||||
'twenty/matching-state-variable': 'error',
|
'twenty/matching-state-variable': 'error',
|
||||||
'twenty/sort-css-properties-alphabetically': 'error',
|
'twenty/sort-css-properties-alphabetically': 'error',
|
||||||
'twenty/styled-components-prefixed-with-styled': 'error',
|
'twenty/styled-components-prefixed-with-styled': 'error',
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const CellCommentChip = (props: CommentChipProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCellWrapper>
|
<StyledCellWrapper>
|
||||||
|
{/* eslint-disable-next-line twenty/no-spread-props */}
|
||||||
<CommentChip {...props} />
|
<CommentChip {...props} />
|
||||||
</StyledCellWrapper>
|
</StyledCellWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const StyledMainLogo = styled.div<StyledMainLogoProps>`
|
|||||||
export const Logo = ({ workspaceLogo, ...props }: Props) => {
|
export const Logo = ({ workspaceLogo, ...props }: Props) => {
|
||||||
if (!workspaceLogo) {
|
if (!workspaceLogo) {
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...props}>
|
<StyledContainer {...props}>
|
||||||
<StyledMainLogo logo="/icons/android/android-launchericon-192-192.png" />
|
<StyledMainLogo logo="/icons/android/android-launchericon-192-192.png" />
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
@ -57,6 +58,7 @@ export const Logo = ({ workspaceLogo, ...props }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...props}>
|
<StyledContainer {...props}>
|
||||||
<StyledMainLogo logo={getImageAbsoluteURIOrBase64(workspaceLogo)} />
|
<StyledMainLogo logo={getImageAbsoluteURIOrBase64(workspaceLogo)} />
|
||||||
<StyledTwentyLogoContainer>
|
<StyledTwentyLogoContainer>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const StyledContent = styled(UIModal.Content)`
|
|||||||
type Props = React.ComponentProps<'div'>;
|
type Props = React.ComponentProps<'div'>;
|
||||||
|
|
||||||
export const AuthModal = ({ children, ...restProps }: Props) => (
|
export const AuthModal = ({ children, ...restProps }: Props) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<UIModal isOpen={true} {...restProps}>
|
<UIModal isOpen={true} {...restProps}>
|
||||||
<StyledContent>{children}</StyledContent>
|
<StyledContent>{children}</StyledContent>
|
||||||
</UIModal>
|
</UIModal>
|
||||||
|
|||||||
@ -11,4 +11,5 @@ const StyledContainer = styled.div`
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
export const FooterNote = (props: Props) => <StyledContainer {...props} />;
|
export const FooterNote = (props: Props) => <StyledContainer {...props} />;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const StyledDescription = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Heading = ({ title, description, ...props }: Props) => (
|
export const Heading = ({ title, description, ...props }: Props) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...props}>
|
<StyledContainer {...props}>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledTitle>{title}</StyledTitle>
|
||||||
{description && <StyledDescription>{description}</StyledDescription>}
|
{description && <StyledDescription>{description}</StyledDescription>}
|
||||||
|
|||||||
@ -115,6 +115,7 @@ export const Table = <Data,>(props: Props<Data>) => {
|
|||||||
const { rtl } = useSpreadsheetImportInternal();
|
const { rtl } = useSpreadsheetImportInternal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledDataGrid direction={rtl ? 'rtl' : 'ltr'} rowHeight={52} {...props} />
|
<StyledDataGrid direction={rtl ? 'rtl' : 'ltr'} rowHeight={52} {...props} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -107,6 +107,7 @@ export const MainButton = ({
|
|||||||
}: MainButtonProps) => {
|
}: MainButtonProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledButton fullWidth={fullWidth} variant={variant} {...props}>
|
<StyledButton fullWidth={fullWidth} variant={variant} {...props}>
|
||||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export const RoundedIconButton = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledIconButton {...props}>
|
<StyledIconButton {...props}>
|
||||||
{<Icon size={theme.icon.size.md} />}
|
{<Icon size={theme.icon.size.md} />}
|
||||||
</StyledIconButton>
|
</StyledIconButton>
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export const AnimatedCheckmark = ({
|
|||||||
height={size}
|
height={size}
|
||||||
>
|
>
|
||||||
<motion.path
|
<motion.path
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...restProps}
|
{...restProps}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke={color ?? theme.grayScale.gray0}
|
stroke={color ?? theme.grayScale.gray0}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export const Checkmark = (props: CheckmarkProps) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...props}>
|
<StyledContainer {...props}>
|
||||||
<IconCheck color={theme.grayScale.gray0} size={14} />
|
<IconCheck color={theme.grayScale.gray0} size={14} />
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -105,6 +105,7 @@ const ColorSchemeSegment = ({
|
|||||||
controls,
|
controls,
|
||||||
...rest
|
...rest
|
||||||
}: ColorSchemeSegmentProps) => (
|
}: ColorSchemeSegmentProps) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledColorSchemeBackground variant={variant} {...rest}>
|
<StyledColorSchemeBackground variant={variant} {...rest}>
|
||||||
<StyledColorSchemeContent animate={controls} variant={variant}>
|
<StyledColorSchemeContent animate={controls} variant={variant}>
|
||||||
Aa
|
Aa
|
||||||
@ -173,6 +174,7 @@ export const ColorSchemeCard = ({
|
|||||||
<StyledMixedColorSchemeSegment
|
<StyledMixedColorSchemeSegment
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<ColorSchemeSegment
|
<ColorSchemeSegment
|
||||||
@ -211,6 +213,7 @@ export const ColorSchemeCard = ({
|
|||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|||||||
@ -137,6 +137,7 @@ export const Dialog = ({
|
|||||||
<StyledDialogContainer
|
<StyledDialogContainer
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
transition={{ damping: 15, stiffness: 100 }}
|
transition={{ damping: 15, stiffness: 100 }}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...rootProps}
|
{...rootProps}
|
||||||
>
|
>
|
||||||
{title && <StyledDialogTitle>{title}</StyledDialogTitle>}
|
{title && <StyledDialogTitle>{title}</StyledDialogTitle>}
|
||||||
@ -151,6 +152,7 @@ export const Dialog = ({
|
|||||||
}}
|
}}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
variant={button.variant ?? 'secondary'}
|
variant={button.variant ?? 'secondary'}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...button}
|
{...button}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export const DialogProvider = ({ children }: React.PropsWithChildren) => {
|
|||||||
{dialogInternal.queue.map((dialog) => (
|
{dialogInternal.queue.map((dialog) => (
|
||||||
<Dialog
|
<Dialog
|
||||||
key={dialog.id}
|
key={dialog.id}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...dialog}
|
{...dialog}
|
||||||
onClose={() => handleDialogClose(dialog.id)}
|
onClose={() => handleDialogClose(dialog.id)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export const DropdownMenuContainer = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledDropdownMenuContainer data-select-disable {...props} anchor={anchor}>
|
<StyledDropdownMenuContainer data-select-disable {...props} anchor={anchor}>
|
||||||
<StyledDropdownMenu ref={dropdownRef} width={width}>
|
<StyledDropdownMenu ref={dropdownRef} width={width}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export const DropdownMenuHeader = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledHeader {...props}>
|
<StyledHeader {...props}>
|
||||||
{StartIcon && (
|
{StartIcon && (
|
||||||
<StyledStartIconWrapper>
|
<StyledStartIconWrapper>
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export const DropdownMenuSearchInput = forwardRef<
|
|||||||
InputHTMLAttributes<HTMLInputElement>
|
InputHTMLAttributes<HTMLInputElement>
|
||||||
>((props, ref) => (
|
>((props, ref) => (
|
||||||
<StyledDropdownMenuSearchInputContainer>
|
<StyledDropdownMenuSearchInputContainer>
|
||||||
|
{/* eslint-disable-next-line twenty/no-spread-props */}
|
||||||
<StyledInput autoComplete="off" placeholder="Search" {...props} ref={ref} />
|
<StyledInput autoComplete="off" placeholder="Search" {...props} ref={ref} />
|
||||||
</StyledDropdownMenuSearchInputContainer>
|
</StyledDropdownMenuSearchInputContainer>
|
||||||
));
|
));
|
||||||
|
|||||||
@ -126,6 +126,7 @@ export const Radio = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...restProps} labelPosition={labelPosition}>
|
<StyledContainer {...restProps} labelPosition={labelPosition}>
|
||||||
<StyledRadioInput
|
<StyledRadioInput
|
||||||
type="radio"
|
type="radio"
|
||||||
|
|||||||
@ -104,6 +104,7 @@ export const ImageInput = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...restProps}>
|
<StyledContainer {...restProps}>
|
||||||
<StyledPicture
|
<StyledPicture
|
||||||
withPicture={!!picture}
|
withPicture={!!picture}
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export const SingleEntitySelect = <
|
|||||||
/>
|
/>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<SingleEntitySelectBase
|
<SingleEntitySelectBase
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...props}
|
{...props}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ const meta: Meta<typeof SingleEntitySelect> = {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...args}
|
{...args}
|
||||||
entitiesToSelect={entities.filter(
|
entitiesToSelect={entities.filter(
|
||||||
(entity) =>
|
(entity) =>
|
||||||
|
|||||||
@ -170,6 +170,7 @@ const TextInputComponent = (
|
|||||||
onChange(event.target.value);
|
onChange(event.target.value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<StyledTrailingIconContainer>
|
<StyledTrailingIconContainer>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const FakeTextInput = ({
|
|||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
return (
|
return (
|
||||||
<TextInputSettings
|
<TextInputSettings
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...props}
|
{...props}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(text) => {
|
onChange={(text) => {
|
||||||
@ -41,6 +42,7 @@ const FakeTextInput = ({
|
|||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
argTypes: { value: { control: false } },
|
argTypes: { value: { control: false } },
|
||||||
args: { value: 'A good value ' },
|
args: { value: 'A good value ' },
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
render: (args) => <FakeTextInput {...args} />,
|
render: (args) => <FakeTextInput {...args} />,
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|||||||
@ -92,6 +92,7 @@ export const PageHeader = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledTopBarContainer {...props}>
|
<StyledTopBarContainer {...props}>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
{!isNavbarOpened && (
|
{!isNavbarOpened && (
|
||||||
|
|||||||
@ -99,18 +99,21 @@ const StyledBackDrop = styled(motion.div)`
|
|||||||
type ModalHeaderProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
type ModalHeaderProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
||||||
|
|
||||||
const ModalHeader = ({ children, ...restProps }: ModalHeaderProps) => (
|
const ModalHeader = ({ children, ...restProps }: ModalHeaderProps) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledHeader {...restProps}>{children}</StyledHeader>
|
<StyledHeader {...restProps}>{children}</StyledHeader>
|
||||||
);
|
);
|
||||||
|
|
||||||
type ModalContentProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
type ModalContentProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
||||||
|
|
||||||
const ModalContent = ({ children, ...restProps }: ModalContentProps) => (
|
const ModalContent = ({ children, ...restProps }: ModalContentProps) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContent {...restProps}>{children}</StyledContent>
|
<StyledContent {...restProps}>{children}</StyledContent>
|
||||||
);
|
);
|
||||||
|
|
||||||
type ModalFooterProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
type ModalFooterProps = React.PropsWithChildren & React.ComponentProps<'div'>;
|
||||||
|
|
||||||
const ModalFooter = ({ children, ...restProps }: ModalFooterProps) => (
|
const ModalFooter = ({ children, ...restProps }: ModalFooterProps) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledFooter {...restProps}>{children}</StyledFooter>
|
<StyledFooter {...restProps}>{children}</StyledFooter>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -203,6 +206,7 @@ export const Modal = ({
|
|||||||
exit="exit"
|
exit="exit"
|
||||||
layout
|
layout
|
||||||
variants={modalVariants}
|
variants={modalVariants}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -160,6 +160,7 @@ export const SnackBar = ({
|
|||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...rootProps}
|
{...rootProps}
|
||||||
>
|
>
|
||||||
<StyledProgressBarContainer>
|
<StyledProgressBarContainer>
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
|
|||||||
layout
|
layout
|
||||||
>
|
>
|
||||||
<SnackBar
|
<SnackBar
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...snackBar}
|
{...snackBar}
|
||||||
onClose={() => handleSnackBarClose(snackBar.id)}
|
onClose={() => handleSnackBarClose(snackBar.id)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export const StepBar = ({ children, activeStep, ...restProps }: StepsProps) => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledContainer {...restProps}>
|
<StyledContainer {...restProps}>
|
||||||
{React.Children.map(children, (child, index) => {
|
{React.Children.map(children, (child, index) => {
|
||||||
if (!React.isValidElement(child)) {
|
if (!React.isValidElement(child)) {
|
||||||
|
|||||||
@ -51,6 +51,7 @@ export const EntityTableColumnMenu = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledColumnMenu {...props} ref={ref}>
|
<StyledColumnMenu {...props} ref={ref}>
|
||||||
<StyledDropdownMenuItemsContainer>
|
<StyledDropdownMenuItemsContainer>
|
||||||
{hiddenTableColumns.map((column) => (
|
{hiddenTableColumns.map((column) => (
|
||||||
|
|||||||
@ -44,5 +44,6 @@ export type AppToolipProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AppTooltip = (props: AppToolipProps) => (
|
export const AppTooltip = (props: AppToolipProps) => (
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
<StyledAppTooltip {...props} />
|
<StyledAppTooltip {...props} />
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export const Default: Story = {
|
|||||||
<p id="hover-text" data-testid="tooltip">
|
<p id="hover-text" data-testid="tooltip">
|
||||||
Hover me!
|
Hover me!
|
||||||
</p>
|
</p>
|
||||||
|
{/* eslint-disable-next-line twenty/no-spread-props */}
|
||||||
<Tooltip {...args} />
|
<Tooltip {...args} />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export const AnimatedEaseIn = ({
|
|||||||
initial={initial}
|
initial={initial}
|
||||||
animate={animate}
|
animate={animate}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -58,6 +58,7 @@ export const AnimatedTextWord = ({ text = '', ...restProps }: Props) => {
|
|||||||
variants={containerAnimation}
|
variants={containerAnimation}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
|
// eslint-disable-next-line twenty/no-spread-props
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{words.map((word, index) => (
|
{words.map((word, index) => (
|
||||||
|
|||||||
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
"effect-components": require("./src/rules/effect-components"),
|
"effect-components": require("./src/rules/effect-components"),
|
||||||
"no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"),
|
"no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"),
|
||||||
|
"no-spread-props": require('./src/rules/no-spread-props'),
|
||||||
"matching-state-variable": require("./src/rules/matching-state-variable"),
|
"matching-state-variable": require("./src/rules/matching-state-variable"),
|
||||||
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
|
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
|
||||||
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
|
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
|
||||||
|
|||||||
31
packages/eslint-plugin-twenty-ts/dist/src/rules/no-spread-props.js
vendored
Normal file
31
packages/eslint-plugin-twenty-ts/dist/src/rules/no-spread-props.js
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const utils_1 = require("@typescript-eslint/utils");
|
||||||
|
const createRule = utils_1.ESLintUtils.RuleCreator(() => "https://docs.twenty.com/developer/frontend/style-guide#no-single-variable-prop-spreading-in-jsx-elements");
|
||||||
|
const noSpreadPropsRule = createRule({
|
||||||
|
create: (context) => ({
|
||||||
|
JSXSpreadAttribute: (node) => {
|
||||||
|
if (node.argument.type === "Identifier") {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: "noSpreadProps",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
name: "no-spread-props",
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: "Disallow passing props as {...props} in React components.",
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
noSpreadProps: `Single variable prop spreading is disallowed in JSX elements.\nPrefer explicitly listing out all props or using an object expression like so: \`{...{ prop1, prop2 }}\`.\nSee https://docs.twenty.com/developer/frontend/style-guide#no-single-variable-prop-spreading-in-jsx-elements for more information.`,
|
||||||
|
},
|
||||||
|
type: "suggestion",
|
||||||
|
schema: [],
|
||||||
|
fixable: "code",
|
||||||
|
},
|
||||||
|
defaultOptions: [],
|
||||||
|
});
|
||||||
|
module.exports = noSpreadPropsRule;
|
||||||
|
exports.default = noSpreadPropsRule;
|
||||||
34
packages/eslint-plugin-twenty-ts/dist/src/tests/no-spread-props.spec.js
vendored
Normal file
34
packages/eslint-plugin-twenty-ts/dist/src/tests/no-spread-props.spec.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const rule_tester_1 = require("@typescript-eslint/rule-tester");
|
||||||
|
const no_spread_props_1 = __importDefault(require("../rules/no-spread-props"));
|
||||||
|
const ruleTester = new rule_tester_1.RuleTester({
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ruleTester.run("no-spread-props", no_spread_props_1.default, {
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
code: "<MyComponent prop1={value} prop2={value} />",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: "<MyComponent {...props} />",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: "noSpreadProps",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@ -2,6 +2,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
"effect-components": require("./src/rules/effect-components"),
|
"effect-components": require("./src/rules/effect-components"),
|
||||||
"no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"),
|
"no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"),
|
||||||
|
"no-spread-props": require("./src/rules/no-spread-props"),
|
||||||
"matching-state-variable": require("./src/rules/matching-state-variable"),
|
"matching-state-variable": require("./src/rules/matching-state-variable"),
|
||||||
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
|
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
|
||||||
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
|
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
|
||||||
|
|||||||
36
packages/eslint-plugin-twenty/src/rules/no-spread-props.ts
Normal file
36
packages/eslint-plugin-twenty/src/rules/no-spread-props.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
||||||
|
|
||||||
|
const createRule = ESLintUtils.RuleCreator(
|
||||||
|
() =>
|
||||||
|
"https://docs.twenty.com/developer/frontend/style-guide#no-single-variable-prop-spreading-in-jsx-elements",
|
||||||
|
);
|
||||||
|
|
||||||
|
const noSpreadPropsRule = createRule({
|
||||||
|
create: (context) => ({
|
||||||
|
JSXSpreadAttribute: (node) => {
|
||||||
|
if (node.argument.type === "Identifier") {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: "noSpreadProps",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
name: "no-spread-props",
|
||||||
|
meta: {
|
||||||
|
docs: {
|
||||||
|
description: "Disallow passing props as {...props} in React components.",
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
noSpreadProps: `Single variable prop spreading is disallowed in JSX elements.\nPrefer explicitly listing out all props or using an object expression like so: \`{...{ prop1, prop2 }}\`.\nSee https://docs.twenty.com/developer/frontend/style-guide#no-single-variable-prop-spreading-in-jsx-elements for more information.`,
|
||||||
|
},
|
||||||
|
type: "suggestion",
|
||||||
|
schema: [],
|
||||||
|
fixable: "code",
|
||||||
|
},
|
||||||
|
defaultOptions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = noSpreadPropsRule;
|
||||||
|
|
||||||
|
export default noSpreadPropsRule;
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { RuleTester } from "@typescript-eslint/rule-tester";
|
||||||
|
|
||||||
|
import noSpreadPropsRule from "../rules/no-spread-props";
|
||||||
|
|
||||||
|
const ruleTester = new RuleTester({
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ruleTester.run("no-spread-props", noSpreadPropsRule, {
|
||||||
|
valid: [
|
||||||
|
{
|
||||||
|
code: "<MyComponent prop1={value} prop2={value} />",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "<MyComponent {...{prop1, prop2}} />",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invalid: [
|
||||||
|
{
|
||||||
|
code: "<MyComponent {...props} />",
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
messageId: "noSpreadProps",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user