8000 Bug: typescript-eslint config types are incompatible with `defineConfig()` types · Issue #10899 · typescript-eslint/typescript-eslint · GitHub
[go: up one dir, main page]

Skip to content

Bug: typescript-eslint config types are incompatible with defineConfig() types #10899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
neuronetio opened this issue Feb 27, 2025 · 5 comments · May be fixed by #11190
Open
2 tasks done

Bug: typescript-eslint config types are incompatible with defineConfig() types #10899

neuronetio opened this issue Feb 27, 2025 · 5 comments · May be fixed by #11190
Labels
accepting prs Go ahead, send a pull request that resolves this issue package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin preset config change Proposal for an addition, removal, or general change to a preset config

Comments

@neuronetio
Copy link
neuronetio commented Feb 27, 2025

Before You File a Proposal Please Confirm You Have Done The Following...

Description

I just installed eslint with typescript-eslint (npm init @eslint/config@latest) - I haven't done anything yet - right away I get this error.

Type '({ readonly rules: Readonly<RulesRecord>; } | Config)[]' is not assignable to type 'Config<RulesRecord>[]'.
  Type '{ readonly rules: Readonly<RulesRecord>; } | Config' is not assignable to type 'Config<RulesRecord>'.
    Type 'Config' is not assignable to type 'Config<RulesRecord>'.
      Types of property 'languageOptions' are incompatible.
        Type 'import("/media/neuronet/projekty/event-conductor/node_modules/@typescript-eslint/utils/dist/ts-eslint/Config").FlatConfig.LanguageOptions | undefined' is not assignable to type 'import("/media/neuronet/projekty/event-conductor/node_modules/eslint/lib/types/index").Linter.LanguageOptions | undefined'.
          Type 'import("/media/neuronet/projekty/event-conductor/node_modules/@typescript-eslint/utils/dist/ts-eslint/Config").FlatConfig.LanguageOptions' is not assignable to type 'import("/media/neuronet/projekty/event-conductor/node_modules/eslint/lib/types/index").Linter.LanguageOptions'.
            Types of property 'parser' are incompatible.
              Type 'LooseParserModule | undefined' is not assignable to type 'Parser | undefined'.
                Type '{ meta?: { name?: string | undefined; version?: string | undefined; } | undefined; parseForESLint(text: string, options?: unknown): { ast: unknown; scopeManager?: unknown; services?: unknown; visitorKeys?: unknown; }; }' is not assignable to type 'Parser | undefined'.
                  Type '{ meta?: { name?: string | undefined; version?: string | undefined; } | undefined; parseForESLint(text: string, options?: unknown): { ast: unknown; scopeManager?: unknown; services?: unknown; visitorKeys?: unknown; }; }' is not assignable to type 'Omit<ESTreeParser, "parseForESLint"> & { parseForESLint(text: string, options?: any): Omit<ESLintParseResult, "ast" | "scopeManager"> & { ...; }; }'.
                    Type '{ meta?: { name?: string | undefined; version?: string | undefined; } | undefined; parseForESLint(text: string, options?: unknown): { ast: unknown; scopeManager?: unknown; services?: unknown; visitorKeys?: unknown; }; }' is not assignable to type '{ parseForESLint(text: string, options?: any): Omit<ESLintParseResult, "ast" | "scopeManager"> & { ast: unknown; scopeManager?: unknown; }; }'.
                      The types returned by 'parseForESLint(...)' are incompatible between these types.
                        Type '{ ast: unknown; scopeManager?: unknown; services?: unknown; visitorKeys?: unknown; }' is not assignable to type 'Omit<ESLintParseResult, "ast" | "scopeManager"> & { ast: unknown; scopeManager?: unknown; }'.
                          Type '{ ast: unknown; scopeManager?: unknown; services?: unknown; visitorKeys?: unknown; }' is not assignable to type 'Omit<ESLintParseResult, "ast" | "scopeManager">'.
                            Types of property 'visitorKeys' are incompatible.
                              Type 'unknown' is not assignable to type 'VisitorKeys | undefined'.ts(2322)

Impacted Configurations

import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";

/** @type {import('eslint').Linter.Config[]} */
export default [
  { files: ["**/*.{js,mjs,cjs,ts}"] },
  { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
];

Additional Info

"typescript-eslint": "^8.25.0"
"@eslint/js": "^9.21.0",
"eslint": "^9.21.0",

I am using vscode with:
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint @ latest
https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode @ latest

I have read that the problem is with the eslint types (#10872) but maybe you will find another way to fix this - so I leave this bug here :P

@neuronetio neuronetio added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin preset config change Proposal for an addition, removal, or general change to a preset config triage Waiting for team members to take a look labels Feb 27, 2025
@gtbuchanan
Copy link

I just ran into the same problem trying to enable @ts-check for my eslint.config.mjs file.

@JoshuaKGoldberg JoshuaKGoldberg added accepting prs Go ahead, send a pull request that resolves this issue and removed triage Waiting for team members to take a look labels Mar 31, 2025
@JoshuaKGoldberg
Copy link
Member

👍 Minimum reproduction here: https://github.com/JoshuaKGoldberg/repros/tree/eslint-defineconfig-with-typescript-eslint

import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";

export default defineConfig(tseslint.configs.recommended);

@kirkwaiblinger
Copy link
Member

Noting for reference that when this gets worked on, we can also switch ourselves internally to defineConfig() and unblock #10935.

@kirkwaiblinger kirkwaiblinger changed the title Type '({ readonly rules: Readonly<RulesRecord>; } | Config)[]' is not assignable to type 'Config<RulesRecord>[]'. Bug: typescript-eslint config types are incompatible with defineConfig() types May 6, 2025
@kirkwaiblinger
Copy link
Member

All right, so the trouble here seems to be that the configs that we export are of our own FlatConfig.Config type...

declare const cjsExport: {
flatConfigs: {
'flat/all': FlatConfig.ConfigArray;
'flat/base': FlatConfig.Config;
'flat/disable-type-checked': FlatConfig.Config;
'flat/eslint-recommended': FlatConfig.Config;
'flat/recommended': FlatConfig.ConfigArray;
'flat/recommended-type-checked': FlatConfig.ConfigArray;
'flat/recommended-type-checked-only': FlatConfig.ConfigArray;
'flat/strict': FlatConfig.ConfigArray;
'flat/strict-type-checked': FlatConfig.ConfigArray;
'flat/strict-type-checked-only': FlatConfig.ConfigArray;
'flat/stylistic': FlatConfig.ConfigArray;
'flat/stylistic-type-checked': FlatConfig.ConfigArray;
'flat/stylistic-type-checked-only': FlatConfig.ConfigArray;
};
parser: FlatConfig.Parser;
plugin: typeof plugin;
};
export = cjsExport;

... which is (intentionally) loose, e.g.

/**
* A loose definition of the ParserModule type for use with configs
* This type intended to relax validation of configs so that parsers that have
* different AST types or scope managers can still be passed to configs
*
* @see {@link LooseRuleDefinition}, {@link LooseProcessorModule}
*/
export type LooseParserModule =
| {
/**
* Information about the parser to uniquely identify it when serializing.
*/
meta?: { [K in keyof ParserMeta]?: ParserMeta[K] | undefined };
/**
* Parses the given text into an AST
*/
parseForESLint(
text: string,
options?: unknown,
): {
// intentionally not using a Record to preserve optionals
[k in keyof ParseResult]: unknown;
};
}
| {
/**
* Information about the parser to uniquely identify it when serializing.
*/
meta?: { [K in keyof ParserMeta]?: ParserMeta[K] | undefined };
/**
* Parses the given text into an ESTree AST
*/
parse(text: string, options?: unknown): unknown;
};

... in ways that unfortunately are incompatible with eslint's defineConfig(). Hence the eventual unknown not being assignable to VisitorKeys | undefined way down the stack. Resolving this specific incompatibility uncovers some others. Also, the typescript-eslint Plugin type isn't assignable to eslint's Plugin type either for similar reasons....

So, our options look roughly like:

  1. Make the typescript-eslint types stricter, so that they are at least assignable to the eslint core types.

    • This may be technically a breaking change for other projects using our types to author plugins/configs.
  2. Leave the typescript-eslint types as-is, but use eslint core's types for our exported configs and plugin.

    • I've prototyped this at fix(typescript-eslint): make exported configs/plugin compatible with defineConfig() #11190

    • This makes tseslint.configs.* and tseslint.plugin compatible with defineConfig() asap but may mean that other projects using our types will still have compatibility problems with defineConfig().

    • Also, this probably means we need to add an explicit dependency on the eslint types somehow, rather than only consuming having eslint as a peer dependency (currently ^8.57.0 || ^9.0.0). Maybe if we get the types directly out of @eslint/config-helpers with ^0.2.0 this is ok though?

  3. Potentially request changes upstream if the upstream types are deemed problematic / too strict.


Thoughts? @typescript-eslint/triage-team , especially @bradzacher

@bradzacher
Copy link
Member

So the reason that the types we use for the config function are loose is because we want to be more permissive in what the types allow.
Essentially the goal was to provide validation of user-configurable things without enforcing that 3rd-party packages played nice with the types.

So for example we have strict types for languageOptions or rules because those are directly user-configurable. However we don't enforce that plugins and parsers exactly match the required shape (which is why the types make heavy use of unknown).

Why do we do this? Well we don't want some 3rd party package to release with bad types and have that cause issues for users. The package's types might be poorly written, or they might be out of date, or they might be written for a newer version of ESLint than the user uses, or they might not even have types (so you're at the mercy of what TS infers from the JS code). Either way we don't want that cruft to impact a user's config typechecking -- so we just define types that describe generalties like "a parser must have a parse function or a parseForESLint function" without enforcing further than that.

However we definitely shouldn't be using these loose types for our exported configs -- the loose types should only be used for the tseslint.config argument.

5ABC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepting prs Go ahead, send a pull request that resolves this issue package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin preset config change Proposal for an addition, removal, or general change to a preset config
Projects
None yet
5 participants
0