Skip to content

[bug] prefer-read-only-props doesn't work well with interface extension & library types #1326

@rdjpalmer

Description

@rdjpalmer

Describe the bug

prefer-read-only-props rule does not work well with extended types. Coming from a react-native/expo project we have a lot of the following code:

interface ComponentProps extends [NativeComponentProps] {
  someProp: string
}

function Component(props: ComponentProps) {
  const { someProp, ...nativeProps } = props;
}

Where NativeComponentProps is say PressableProps or ViewProps. These come with react-native and of course aren't readonly so the rule fails.

I added a generic to force them to be read only like so:

type DeepReadOnly<T> = {
  readonly [P in keyof T]: T[P] extends (infer U)[]
    ? ReadonlyArray<DeepReadOnly<U>>
    : T[P] extends ReadonlyArray<infer U>
      ? ReadonlyArray<DeepReadOnly<U>>
      : T[P] extends object
        ? DeepReadOnly<T[P]>
        : T[P];
};

type ReadOnlyPressableProps = DeepReadOnly<Pressable>;

However, while this appears to work when you inspect the types of ReadOnlyPressableProps, the eslint rule still reports the issue.

Reproduction

  1. Clone eslint-plugin-react-x-prefer-read-only-props-false-negative

  2. Run npm install --force (required to ignore some peer dependency issues)

  3. Open in your editor

  4. Open src/Component.tsx

  5. You should now see an eslint warning that Component's props are not readonly.
    Alternatively, using eslint's cli:

  6. Clone eslint-plugin-react-x-prefer-read-only-props-false-negative

  7. Run npm install --force (required to ignore some peer dependency issues)

  8. Run npm run lint

Expected behavior

No warning should be reported

Platform and versions

node -v
v22.14.0



{
  "eslint": "^9.39.1",
  "eslint-plugin-react-x": "^2.3.7",
  "typescript-eslint": "^8.47.0"
}

Stack trace


Additional context

Note that if you use type instead of interface the issue seems to go away:

type DeepReadOnly<T> = {
  readonly [P in keyof T]: T[P] extends (infer U)[]
    ? ReadonlyArray<DeepReadOnly<U>>
    : T[P] extends ReadonlyArray<infer U>
      ? ReadonlyArray<DeepReadOnly<U>>
      : T[P] extends object
        ? DeepReadOnly<T[P]>
        : T[P];
};

interface PressableProps {
  testID: string;
}

type ReadonlyPressableProps = DeepReadOnly<PressableProps>;

type ComponentProps = {
  readonly name: string;
} & ReadonlyPressableProps;

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: BugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions