Skip to content

feat: Extendable types for rules and settings configs#19843

Draft
nzakas wants to merge 15 commits intomainfrom
new-types
Draft

feat: Extendable types for rules and settings configs#19843
nzakas wants to merge 15 commits intomainfrom
new-types

Conversation

@nzakas
Copy link
Copy Markdown
Member

@nzakas nzakas commented Jun 11, 2025

Prerequisites checklist

What is the purpose of this pull request? (put an "X" next to an item)

[x] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[x] Add something to the core
[ ] Other, please explain:

What changes did you make? (Give an overview)

  • Updated @eslint/core
  • Changed config types to use RulesConfig and SettingsConfig from @eslint/core
  • Added documentation describing how to use these new types

fixes #19721
fixes #19662

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

One thing I couldn't figure out how to do was how to merge all of the rule config entries from rules.d.ts into the RulesConfig from @eslint/core without duplicating the code. I think that would be ideal, but I don't think it's a blocker for this PR.

@nzakas nzakas requested a review from a team as a code owner June 11, 2025 21:01
@github-project-automation github-project-automation Bot moved this to Needs Triage in Triage Jun 11, 2025
@eslint-github-bot eslint-github-bot Bot added the feature This change adds a new feature to ESLint label Jun 11, 2025
@nzakas nzakas requested a review from Copilot June 11, 2025 21:02
@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 11, 2025

Deploy Preview for docs-eslint ready!

Name Link
🔨 Latest commit 90d23b4
🔍 Latest deploy log https://app.netlify.com/projects/docs-eslint/deploys/685efa3d252c2000086c5fff
😎 Deploy Preview https://deploy-preview-19843--docs-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

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 introduces extendable type support for plugin rules and settings by updating the core dependency and adding corresponding documentation.

  • Bumps the @eslint/core version to enable new RulesConfig and SettingsConfig types
  • Adds docs showing how to augment RulesConfig and SettingsConfig in plugins

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
package.json Updated @eslint/core dependency from ^0.14.0 to ^0.15.0
docs/src/extend/plugins.md Added sections and examples for extending RulesConfig and SettingsConfig
Comments suppressed due to low confidence (2)

docs/src/extend/plugins.md:194

  • There’s an extra { here in the code snippet. Remove it or wrap these entries in the proper object/array structure so the example compiles.
{

docs/src/extend/plugins.md:424

  • This snippet also has an extra {. Adjust the example to show a valid defineConfig call with the correct object or array nesting.
{

Comment thread docs/src/extend/plugins.md Outdated
Comment thread lib/types/index.d.ts
type RuleEntry<Options extends any[] = any[]> =
| RuleSeverity
| RuleSeverityAndOptions<Options>;
type RuleEntry<Options extends any[] = any[]> = RuleConfig<Options>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To eliminate duplication and avoid a breaking change.

Comment thread lib/types/index.d.ts
interface RulesRecord {
[rule: string]: RuleEntry;
}
type RulesRecord = RulesConfig;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same here. Don't want to delete the type, just alias the new type instead.

@lumirlumir lumirlumir added the accepted There is consensus among the team that this change meets the criteria for inclusion label Jun 12, 2025
Comment thread docs/src/extend/plugins.md Outdated
Comment thread docs/src/extend/plugins.md Outdated
Comment thread lib/types/index.d.ts Outdated
* @see [Settings](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#adding-shared-settings)
*/
settings?: { [name: string]: any } | undefined;
settings?: SettingsConfig | undefined;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the interface of eslintrc configs. I think this change should be made here instead:

settings?: Record<string, unknown>;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oops, good catch!

@mdjermanovic mdjermanovic moved this from Needs Triage to Implementing in Triage Jun 12, 2025
@mdjermanovic mdjermanovic added the types Related to TypeScript types label Jun 12, 2025
Comment thread docs/src/extend/plugins.md Outdated

declare module "@eslint/core" {
interface RulesConfig {
"example/dollar-sign": RuleConfig<["always" | "never"]>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"example/dollar-sign": RuleConfig<["always" | "never"]>;
"example/dollar-sign": RuleConfig<Partial<["always" | "never"]>>;

Otherwise, a configuration like "example/dollar-sign": ["error"] would be invalid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Are you sure about that? RuleConfig is defined as follows:

https://github.com/eslint/rewrite/blob/37e3e14499c1d42c0c420dfbabac3a10a9a82925/packages/core/src/types.ts#L680-L682

To me, that reads like just having a severity would still be valid even without Partial?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To me, that reads like just having a severity would still be valid even without Partial?

It would be valid as the whole value, i.e., not wrapped in an array.

I was testing this way:

// eslint.config.ts

import type { RuleConfig } from "@eslint/core";

declare module "@eslint/core" {
	interface RulesConfig {
		"example/dollar-sign": RuleConfig<["always" | "never"]>;
	}
}

import { defineConfig } from "eslint/config";
//import example from "eslint-plugin-example";

export default defineConfig([
	{
		files: ["**/*.js"],
		//plugins: { example },
		rules: {
			"example/dollar-sign": ["error"],
		},
	},
]);

image

Copy link
Copy Markdown
Member Author

@nzakas nzakas Jun 12, 2025

Choose a reason for hiding this comment

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

Ah, I see! It seems like we could modify RuleConfig like this:

export type RuleConfig<RuleOptions extends unknown[] = unknown[]> =
	| Severity
+       | [Severity]
	| [Severity, ...RuleOptions];

It would be one less thing rule developers need to remember?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

https://github.com/eslint/rewrite/blob/37e3e14499c1d42c0c420dfbabac3a10a9a82925/packages/core/src/types.ts#L680-L682

Perhaps that should have been Severity | [Severity, ...Partial<RuleOptions>] so that rule types don't need to make options optional. Though that would assume that options are always optional, which doesn't necessarily has to be the case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To have the eslint package import the ESM version of the @eslint/core types, it's also possible to use the resolution-mode import attribute, which is stable since TypeScript 5.3 according to the docs. That would be this change in lib/types/index.d.ts:

-} from "@eslint/core";
+} from "@eslint/core" with { "resolution-mode": "import" };

And in that case we'd probably need a new .mts test file to test the extendable interface inside an ESM module.

I tested this method and it passed both npm run test:types and npm run lint:types (after moving the new tests to an .mts file). I haven't checked the type integration tests though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ooh nice!

I suppose the question then is which TypeScript versions do we want to support?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good question. Currently, we don't have a statement or policy that specifies which TypeScript versions are supported, unlike what we have for Node.js or jiti. I think it would be good to add this for TypeScript as well, regardless of which features we choose to support.

As far as I know, there's no reliable way to import types from an ESM module into a CommonJS module that works without resolution-mode. So, if we want to support TypeScript versions before 5.3, we'll need to stick to CommonJS-style core types. Alternatively, we could switch the ESLint types to ESM, but that would only make sense if we convert the whole repo to ESM.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Supporting a minimum TypeScript version is a lot less work than converting the package to ESM, so let's discuss that option:
#19920

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now that TypeScript < 5.3 is no longer supported and the CommonJS core types re-export the ESM types I think it should be fine to move forward with this PR. Some of the comments in this thread seem no longer relevant.

@sam3k
Copy link
Copy Markdown
Contributor

sam3k commented Jun 17, 2025

Per 2025-06-23 meeting, @mdjermanovic will investigate further into this.

@mdjermanovic mdjermanovic mentioned this pull request Jun 25, 2025
1 task
@nzakas nzakas marked this pull request as ready for review June 27, 2025 15:22
Comment thread tests/lib/types/types.test.ts
nzakas and others added 2 commits June 27, 2025 14:22
Co-authored-by: Milos Djermanovic <[email protected]>
Co-authored-by: Milos Djermanovic <[email protected]>
mdjermanovic
mdjermanovic previously approved these changes Jun 27, 2025
Copy link
Copy Markdown
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

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

LGTM, thanks! Leaving it open for @fasttime to verify.

@mdjermanovic mdjermanovic moved this from Implementing to Second Review Needed in Triage Jun 27, 2025
Comment thread docs/src/extend/plugins.md Outdated
Comment thread docs/src/extend/plugins.md Outdated
Co-authored-by: Francesco Trotta <[email protected]>
Co-authored-by: Francesco Trotta <[email protected]>
@mdjermanovic
Copy link
Copy Markdown
Member

Oops, I didn't see #19843 (comment)

@Shinigami92
Copy link
Copy Markdown
Contributor

@nzakas is there anything I can help with here? I have HIGH interest in this getting published 🚀
I will help the plugin ecosystem to adopt this type augmentations.

I will spend $50 to @nzakas via GitHub Sponsoring when this gets published. 💸

archlinux-github pushed a commit to archlinux/neoasknot that referenced this pull request Jul 15, 2025
This reverts commit a04cc99.

Now using `svelte.config.ts`, but you cannot import a TypeScript file in
a JavaScript file.

Seems eslint/eslint#19843 would be needed before
we can switch to `eslint.config.ts`.
@nzakas
Copy link
Copy Markdown
Member Author

nzakas commented Jul 21, 2025

@Shinigami92 there's a lot we need to figure out before we can ship this:
#19920

@github-actions
Copy link
Copy Markdown

Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.

@github-actions github-actions Bot added the Stale label Jul 31, 2025
@nzakas nzakas self-assigned this Aug 4, 2025
@lumirlumir lumirlumir mentioned this pull request Jan 2, 2026
1 task
@mdjermanovic mdjermanovic moved this from Second Review Needed to Implementing in Triage Mar 27, 2026
@mdjermanovic mdjermanovic self-requested a review March 27, 2026 08:05
Copy link
Copy Markdown
Member

@fasttime fasttime left a comment

Choose a reason for hiding this comment

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

There are merge conflicts in the type declarations (index.d.ts) and in package.json, but it looks like all changes in those two files have been already incorporated in the main branch a while ago, so the remaining work is likely just updating documentation and type tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accepted There is consensus among the team that this change meets the criteria for inclusion feature This change adds a new feature to ESLint types Related to TypeScript types

Projects

Status: Implementing

Development

Successfully merging this pull request may close these issues.

Change Request: Add per-rule types Change Request: Can we make the Linter.Settings an interface for module declaration merging?

7 participants