Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add the ability to pass plugin rule types to the config
- Loading branch information
1 parent
96b6e74
commit 68e4da9
Showing
2 changed files
with
332 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
import type { FlatConfig } from '../../src/ts-eslint/Config'; | ||
|
||
type NoExplicitAny = | ||
| [] | ||
| [ | ||
{ | ||
/** Whether to enable auto-fixing in which the \`any\` type is converted to the \`unknown\` type. */ | ||
fixToUnknown?: boolean; | ||
/** Whether to ignore rest parameter arrays. */ | ||
ignoreRestArgs?: boolean; | ||
}, | ||
]; | ||
type ClassLiteralPropertyStyle = [] | ['fields' | 'getters']; | ||
type PaddingBetwenLineStatements = { | ||
blankLine: 'always' | 'any' | 'never'; | ||
next: string; | ||
prev: string; | ||
}[]; | ||
|
||
/* | ||
eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- | ||
this needs to be an object type so it has the implicit index sig | ||
an interface that extends PluginRuleTypes doesn't work either because that breaks | ||
the ExtractRulesFromPluginRuleTypesWithDefault condition. | ||
we expect most users would define this inline anyway - which avoids the issue: | ||
FlatConfig.Config<{ ... }> | ||
*/ | ||
type Plugins = { | ||
'@typescript-eslint'?: { | ||
rules: { | ||
'no-explicit-any': NoExplicitAny; | ||
'class-literal-property-style': ClassLiteralPropertyStyle; | ||
'padding-line-between-statements': PaddingBetwenLineStatements; | ||
}; | ||
}; | ||
}; | ||
|
||
declare function test(arg: FlatConfig.Config<Plugins>): void; | ||
|
||
// no-array style works | ||
test({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': 'error', | ||
'@typescript-eslint/class-literal-property-style': 'error', | ||
'@typescript-eslint/padding-line-between-statements': 'error', | ||
}, | ||
}); | ||
|
||
// array-with-no-options style works | ||
test({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': ['error'], | ||
'@typescript-eslint/class-literal-property-style': ['error'], | ||
'@typescript-eslint/padding-line-between-statements': ['error'], | ||
}, | ||
}); | ||
|
||
// not all rules need to be specified | ||
test({ | ||
rules: {}, | ||
}); | ||
test({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': 'error', | ||
}, | ||
}); | ||
|
||
// unknown rules are allowed | ||
test({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': 'error', | ||
'unknown/hello': ['error', 'i live in a giant bucket'], | ||
}, | ||
}); | ||
|
||
// options are validated as defined | ||
test({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': [ | ||
'error', | ||
{ | ||
fixToUnknown: true, | ||
// @ts-expect-error -- I errored like I expected because the type is wrong! | ||
ignoreRestArgs: 'how did i get this wrong?', | ||
}, | ||
], | ||
'@typescript-eslint/class-literal-property-style': ['error', 'fields'], | ||
'@typescript-eslint/padding-line-between-statements': [ | ||
'error', | ||
{ blankLine: 'always', next: 'Foo', prev: '*' }, | ||
{ | ||
blankLine: 'never', | ||
next: 'Bar', | ||
// @ts-expect-error -- I errored like I expected because the type is wrong! | ||
prev: 1, | ||
}, | ||
], | ||
}, | ||
}); | ||
test({ | ||
rules: { | ||
'@typescript-eslint/class-literal-property-style': [ | ||
'error', | ||
'getters', | ||
// @ts-expect-error -- I errored because I was unexpected! | ||
'extra arg woopsie', | ||
], | ||
}, | ||
}); | ||
|
||
declare function testNoTypes(arg: FlatConfig.Config): void; | ||
// unknown rules are allowed | ||
testNoTypes({ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': 'error', | ||
'unknown/hello': ['error', 'wtf'], | ||
}, | ||
}); | ||
declare const arg: FlatConfig.Config; | ||
arg.rules satisfies Partial<FlatConfig.Rules | undefined>; | ||
|
||
// the higher-order composition types pass the types through as expected | ||
const _configFile1: FlatConfig.ConfigFile<Plugins> = [ | ||
{ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': [ | ||
'error', | ||
{ | ||
fixToUnknown: true, | ||
// @ts-expect-error -- I errored like I expected because the type is wrong! | ||
ignoreRestArgs: 'how did i get this wrong?', | ||
}, | ||
], | ||
'@typescript-eslint/class-literal-property-style': ['error', 'fields'], | ||
}, | ||
}, | ||
]; | ||
|
||
// this works - but sadly TS doesn't provide autocomplete or show the jsdoc comments | ||
// I think it's due to the indirection of the `Promise.resolve`? | ||
const _configFile2: FlatConfig.ConfigFile<Plugins> = () => | ||
Promise.resolve([ | ||
{ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': [ | ||
'error', | ||
{ | ||
fixToUnknown: true, | ||
}, | ||
], | ||
'@typescript-eslint/class-literal-property-style': ['error', 'fields'], | ||
}, | ||
}, | ||
]); | ||
// this works and provides autocomplete! | ||
// eslint-disable-next-line @typescript-eslint/require-await | ||
const _configFile3: FlatConfig.ConfigFile<Plugins> = async () => [ | ||
{ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': [ | ||
'error', | ||
{ | ||
fixToUnknown: true, | ||
}, | ||
], | ||
'@typescript-eslint/class-literal-property-style': ['error', 'fields'], | ||
}, | ||
}, | ||
]; | ||
// @ts-expect-error - sadly TS will kind of error here on the declaration for this case | ||
// the only way to make this better is by using an explicit return type | ||
// eslint-disable-next-line @typescript-eslint/require-await | ||
const _configFile4: FlatConfig.ConfigFile<Plugins> = async () => [ | ||
{ | ||
rules: { | ||
'@typescript-eslint/no-explicit-any': [ | ||
'error', | ||
{ | ||
ignoreRestArgs: 'how did I get this wrong?', | ||
}, | ||
], | ||
}, | ||
}, | ||
]; | ||
|
||
// untyped config entries work with typed config entries | ||
declare const configWithNoPlugin: FlatConfig.Config; | ||
declare const configWithPlugin: FlatConfig.Config<Plugins>; | ||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
declare const configWithOtherPlugin1: FlatConfig.Config<{}>; | ||
declare const configWithOtherPlugin2: FlatConfig.Config<{ | ||
other: { | ||
rules: { | ||
'rule-name': []; | ||
}; | ||
}; | ||
}>; | ||
const _configFile5: FlatConfig.ConfigArray<Plugins> = [ | ||
configWithNoPlugin, | ||
configWithPlugin, | ||
configWithOtherPlugin1, | ||
// @ts-expect-error -- this is disallowed because the "other" plugin isn't declared in the parent generic | ||
configWithOtherPlugin2, | ||
]; | ||
|
||
// multiple plugins can be provided via an intersection | ||
declare function combinedPlugins( | ||
arg: FlatConfig.Config< | ||
{ | ||
plugin1: { | ||
rules: { | ||
'rule-name1': [1]; | ||
'rule-name2': [2]; | ||
}; | ||
}; | ||
} & { | ||
plugin2: { | ||
rules: { | ||
'rule-name3': [3]; | ||
'rule-name4': [4]; | ||
}; | ||
}; | ||
} | ||
>, | ||
): void; | ||
combinedPlugins({ | ||
rules: { | ||
'plugin1/rule-name1': ['error', 1], | ||
'plugin1/rule-name2': ['error', 2], | ||
'plugin2/rule-name3': ['error', 3], | ||
// TODO - WTF????? NO ERROR? | ||
'plugin2/rule-name4': ['error', 99], | ||
}, | ||
}); | ||
combinedPlugins({ | ||
rules: { | ||
'plugin1/rule-name1': ['error', 1], | ||
// TODO - WTF????? NO ERROR? | ||
'plugin1/rule-name2': ['error', 99], | ||
'plugin2/rule-name3': ['error', 3], | ||
'plugin2/rule-name4': ['error', 4], | ||
}, | ||
}); | ||
combinedPlugins({ | ||
rules: { | ||
'plugin1/rule-name1': ['error', 1], | ||
// TODO - WTF????? NO ERROR? | ||
'plugin1/rule-name2': ['error', 99], | ||
'plugin2/rule-name3': ['error', 3], | ||
// @ts-expect-error -- incorrect rule option | ||
'plugin2/rule-name4': ['error', 99], | ||
}, | ||
}); |