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
feat: add new package for strictly typing configuration files #1296
Conversation
This looks very cool! While I've still got my hopes on the RFC being accepted, this is definitely useful, and it's also cool to see an actual real-world implementation of the RFC. When I'm off work & having some spare time, I'll look to type |
namespace ESLintConfig { | ||
export type RuleLevel = 0 | 1 | 2 | 'off' | 'warn' | 'error'; | ||
export type RuleLevelAndNoOptions = RuleLevel | [RuleLevel]; | ||
export type RuleLevelAndUnknownOptions = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add a rule-type for known options so that rule-authors can add their own definition:
export type RuleLevelAndKnownOptions<TRuleName extends keyof RuleStore> = RuleStore[TRuleName];
interface RuleStore
{
"space": [
RuleLevel,
...Array<(
"tab" |
number |
{
SwitchCase: number
})>
];
}
[name: string]: unknown; | ||
} | ||
|
||
interface Rules { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure Rules
can also handle known options:
type Rules = {
[name: string]: RuleLevel | RuleLevelAndUnknownOptions;
} & {
[RuleName in keyof RuleStore]?: RuleLevel | RuleLevelAndKnownOptions<RuleName>
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR looks great to me but it would be nice to also have rule-types implemented.
Please refer to my comments.
Hey @manuth - have a look at the example config I've got here The premise behind this PR is that a plugin author can declare types for their rules via interface declaration merging. I.e. within the declare global {
namespace ESLintConfig {
interface Rules {
'eslint-comments/disable-enable-pair'?:
| ESLintConfigBase.RuleLevelAndNoOptions
| [
ESLintConfigBase.RuleLevel,
{
allowWholeFile?: boolean;
},
];
'eslint-comments/no-aggregating-enable'?: ESLintConfigBase.RuleLevelAndNoOptions;
'eslint-comments/no-duplicate-disable'?: ESLintConfigBase.RuleLevelAndNoOptions;
'eslint-comments/no-restricted-disable'?:
| ESLintConfigBase.RuleLevelAndNoOptions
| [ESLintConfigBase.RuleLevel, ...string[]];
'eslint-comments/no-unlimited-disable'?: ESLintConfigBase.RuleLevelAndNoOptions;
'eslint-comments/no-unused-disable'?: ESLintConfigBase.RuleLevelAndNoOptions;
'eslint-comments/no-unused-enable'?: ESLintConfigBase.RuleLevelAndNoOptions;
'eslint-comments/no-use'?:
| ESLintConfigBase.RuleLevelAndNoOptions
| [
ESLintConfigBase.RuleLevel,
{
allow?: (
| 'eslint'
| 'eslint-disable'
| 'eslint-disable-line'
| 'eslint-disable-next-line'
| 'eslint-enable'
| 'eslint-env'
| 'exported'
| 'global'
| 'globals'
)[];
},
];
}
}
} There are two problems with using
Both of these are an instant non-starter for using types, because you cannot declare an With the current solution, a user can just do the following in their +// @ts-check
+const typedConfig = require('@typescript-eslint/typed-config');
-module.exports = {
+module.exports = typedConfig({
// existing config...
-}
+}) |
Though in my suggestion This leaves me with one question: will changing |
I guess my question is - what's the difference between your suggested |
The only difference is that you don't have to add This might be a good idea because if I understand correctly this must be possible by design of eslint. |
Ah sorry, I misunderstood your intention. I understand now.
In the general case, yes, this holds true; generally rules are set up with optional options because authors provide default config for a rule so it's easier to configure, but it is not actually a requirement. eslint uses JSON schemas to validate the options, and it's possible to define a JSON schema which requires the user provides an option. For example, I very recently did this accidentally for a rule: #1472 Do I know of any rules that actually do this intentionally? Nope. |
@bradzacher I would love to champion this, I have many ideas and come from a background of spoon feeding typescript to javascript developers so I think I could do well. A few initial thoughts:
would the best way to handle this to open a draft PR from my forked repo? do I make it to |
These make the assumption that a user has a typescript-only codebase. My original thinking for the first cut of this was to just keep it simple to start with and see how it grows. This will require adoption from the ecosystem for it to flourish, so we don't want to go all-in and put mountains of effort, only to find out that no plugins want to add types for their rule configs. That's probably the most difficult part of this particular project - figuring out how we can make it dead simple for plugin owners to provide types for their plugin (or similarly for the community to add types to DefinitelyTyped for the plugin). |
I realized when writing the docs that explaining the parts of the config they need to setup and saying the function is a no-op is far simpler than explaining what it adds and why, so definitely will stay a no-op. Very interesting to hear that these cases exist though. The way I see it, plugins already provide JSON schema for the rules and libraries exist to convert this to typescript definitions. It's still far from production ready and some updates to |
Yeah that package is exactly what I've used to do this. https://github.com/bradzacher/eslint-config-brad/tree/master/src/types My work in the above package was the inspiration for this work and validation that it can work! If we can make it all work ambiently then that'd be ace. But we could always fall back to a generic which takes a union of the plugin defs. We'd also need some assistance from plugin authors to define correct schemas - some of them define schemas that produce bad types eg jsx-eslint/eslint-plugin-react#2340 |
Well I mean allowing an empty object to be specified isn't exactly a bad type, the auto generated type does match the actual allowed schema in those cases. The only "bad schema" I've seen is our own
As far as I can tell the config file will need at minimum a |
If I understand it correctly, the new config simplification ESLint are going with has you explicitly |
For ESLint, I can use: module.exports = /** @type {import("eslint").Linter.BaseConfig} */ ({
// ...
}); ... to get some basic code completion, etc. However, this doesn't include some of the configuration extensions from this project – is there a similar type that I could use for TypeScript-ESLint? |
@glen-84 we have our own package (which is used by all our tooling) that includes complete types for ESLint, extended to include our stuff. typescript-eslint/packages/experimental-utils/src/ts-eslint/Linter.ts Lines 117 to 184 in e4d737b
I believe the import path would be import('@typescript-eslint/experimental-utils').TSESLint.Linter.Config |
Cool, thanks. Will it ever be renamed from "experimental"? 😛 |
At this point I almost see it as an inside joke and I kind of don't want to change it 🙃 a7a03ce#diff-75b5ce2fecf42d2221d1f324712f902a177a59a84040d8bbad7390c32cb37a12R13 3eb5d45#diff-75b5ce2fecf42d2221d1f324712f902a177a59a84040d8bbad7390c32cb37a12R13 f5c271f#diff-75b5ce2fecf42d2221d1f324712f902a177a59a84040d8bbad7390c32cb37a12R19 |
Following on from eslint/rfcs#50, as it doesn't look like that idea is going to get in by itself, and the proposed alternative (new config file) would probably take quite a while.
cc interested parties - @G-Rath @mysticatea @kaicataldo
Playing with providing a framework to create a strictly typed config file.
The function is just an identity function, but via
// @ts-check
, typescript will consume the declared types for it, and strictly type the config object.Plugin authors will be able to use declaration merging in order to augment our types, and provide strict types for their rules (see
.eslintrc-example-test-temp.ts
for a working example).I.e. an eslint plugin can provide types themselves, within their package, or users can provide types for them via DefinitelyTyped.
If people like this idea, we can also add in tooling to this package to help plugin maintainers automatically generate types based on their json schemas (see https://github.com/bradzacher/eslint-config-brad/tree/master/src/types which is generated by https://github.com/bradzacher/eslint-config-brad/blob/master/scripts/generateTypes.ts)