Skip to content

Conversation

@lumirlumir
Copy link
Member

@lumirlumir lumirlumir commented Dec 31, 2025

Prerequisites checklist

What is the purpose of this pull request?

Which packages would you like to change?

  • @eslint/compat
  • @eslint/config-array
  • @eslint/config-helpers
  • @eslint/core
  • @eslint/mcp
  • @eslint/migrate-config
  • @eslint/object-schema
  • @eslint/plugin-kit

What problem do you want to solve?

In the public API of @eslint/object-schema, the return types of MergeStrategy.overwrite, MergeStrategy.replace, and MergeStrategy.assign are currently typed as any, which feels too broad and could be narrowed to a clearer type.

What do you think is the correct solution?

For example:

MergeStrategy.overwrite always returns value2; MergeStrategy.replace always returns either value1 or value2; and MergeStrategy.assign always returns the merged object produced by Object.assign().

I think if we use a template for these static functions, we can make the types clearer.

What changes did you make? (Give an overview)

In this PR, I've tightened the types for MergeStrategy in object-schema.

The previous return type any was too broad, and from a TypeScript perspective, using any is generally not recommended.

By using templates, the return types can be narrowed, so I went ahead with this approach.

Related Issues

N/A

Is there anything you'd like reviewers to focus on?

N/A

@eslint-github-bot eslint-github-bot bot added the bug Something isn't working label Dec 31, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Dec 31, 2025
@lumirlumir lumirlumir moved this from Needs Triage to Implementing in Triage Dec 31, 2025
* @template TValue2 The type of the value from the second object key.
* @param {TValue1} value1 The value from the first object key.
* @param {TValue2} value2 The value from the second object key.
* @returns {TValue1 | TValue2} The second value if it is defined.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the replace static method always returns value2 when value2 is not undefined, the return type could be narrowed.

However, there are edge cases where value2 may be, for example, string | undefined, and I don’t see a clear way to express a more specific type for this function right now.

Using TValue1 | TValue2 as the return type seems like the most general approach here. If there’s a way to narrow it more precisely, I’m happy to follow it.

Comment on lines +44 to +45
* @template {Record<string | number | symbol, unknown>} TValue1 The type of the value from the first object key.
* @template {Record<string | number | symbol, unknown>} TValue2 The type of the value from the second object key.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the JSDoc comments and the README.md, it seems the restriction on value1 and value2 is that they must be objects with properties, but I'm not entirely sure.

* Merges two properties by assigning properties from the second to the first.

- `"assign"` - use `Object.assign()` to merge the two values into one object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like value1 can be undefined. This is the relevant test case:

schema = new ObjectSchema({
foo: {
merge: "assign",
validate() {},
},
});
const result = schema.merge(
{ foo: { bar: true } },
{ foo: { baz: false } },
);

Stepping through with the debugger shows that MergeStrategy.assign() is invoked twice:

  1. with value1 = undefined and value2 = { bar: true }
  2. with value1 = { bar: true } and value2 = { baz: false }

Comment on lines +37 to +40
MergeStrategy.replace(1, 2) satisfies number;
MergeStrategy.replace("a", "b") satisfies string;
MergeStrategy.replace(true, false) satisfies boolean;
MergeStrategy.replace({ a: 1 }, { b: 2 }) satisfies Record<string, number>;
Copy link
Member Author

@lumirlumir lumirlumir Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally wrote this type test a little loosely, per the #348 (comment), in case the types can be narrowed further in the future.

For example, we can use the following test case instead to ensure its behavior:

- MergeStrategy.replace(1, 2) satisfies number
+ MergeStrategy.replace(1, 2) satisfies 1 | 2

(FYI: this type test is also ensured and fails if we revert this change.)

However, If narrowing the type test is needed here as well, I'm happy to do that.

@lumirlumir lumirlumir marked this pull request as ready for review January 2, 2026 14:44
Copilot AI review requested due to automatic review settings January 2, 2026 14:44
@lumirlumir lumirlumir moved this from Implementing to Needs Triage in Triage Jan 2, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves type safety in the @eslint/object-schema package by replacing the overly broad any return types in MergeStrategy methods with more precise generic types. This enhances the developer experience by providing better type inference and compile-time checking when using these merge strategies.

  • Uses TypeScript generics to narrow return types from any to specific inferred types
  • Adds comprehensive type tests to verify the new type behavior
  • Maintains backward compatibility as the runtime behavior is unchanged

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/object-schema/src/merge-strategy.js Updated JSDoc type annotations to use generic templates for overwrite, replace, and assign methods, providing more precise return types
packages/object-schema/tests/types/types.test.ts Added comprehensive type tests to validate that the new generic types correctly infer return types and properly reject invalid type assertions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@fasttime fasttime moved this from Needs Triage to Triaging in Triage Jan 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accepted bug Something isn't working

Projects

Status: Triaging

Development

Successfully merging this pull request may close these issues.

3 participants