Flat Configs and Shareable Configs - plugin namespacing and rule config duplication #17766
Replies: 2 comments 3 replies
-
FWIW I'd suggest having the plugins be the ones to define their own names. That way plugin authors can have a guarantee of what'll be shown to users, other plugins have a guarantee of what to refer to the rules as, and users have a guarantee of what to look up for in error messages and the like. |
Beta Was this translation helpful? Give feedback.
-
I think it's too early to call this a problem. We never promised that things would work the same way with flat config as they do with eslintrc. People kept asking for more control over how they composed their configs, and flat config gives them the ultimate control. But as the saying goes, with great power comes great responsibility. If you want the flexibility you also have to accept the complexity. My theory and expectation is that this will actually be a minor issue. In my experience, most people writing configs (and indeed, those writing shared configs) tend to copy-paste the examples from the plugins they're using. So if you have documentation on a plugin that looks like this: import example from "eslint-plugin-example";
export default [
{
plugins: { example }
}
]; Then most of the time in most cases, people will end up using the As for responsibilities -- I think there are two sets of responsibilities here:
Now, I'm not saying no changes are necessary to flat config and everything will 100% fine going forward. What I am saying is I think it's too soon to make any definitive statements about what will and won't be a problem for most users. Once we get a v9.0.0-alpha out, I expect we'll get a flood of feedback to sift through and I think at that point we can start to figure out what changes, if any, may be necessary. |
Beta Was this translation helpful? Give feedback.
-
I've been working on adding support for flat configs to @typescript-eslint. The biggest point of migration is converting our shared configs across to the flat config style of directly referencing the plugin/parser rather than stringly referencing them.
I immediately ran into one concern as I setup the base config - plugin namespaces.
It appears to me like there's going to be a major problem with shareable configs and plugin namespaces. Currently the ecosystem of shareable configs works because ESLint enforced that all rules were namespaced in the same way using the same identifier derived from the plugin name - eg for our project the namespace is
@typescript-eslint/
.However with flat configs that restriction no longer exists and instead it's the config that gets to choose what namespace each plugin uses. In an isolated config file this is awesome! I can alias and shorten names if I like to clarify when I want to - brilliant!
However when I look at the broader ecosystem which heavily relies upon shareable configs I see a massive problem - how do you enforce that all configurations declare the plugin with exactly the same namespace?
Imagine the following shared configs
These configs are written in the "current style" of configs - where the config does what it pleases and directly controls everything.
If a user were to consume both of these configs at the same time:
And then linted a file, they'll get the following output:
Big oof! The user didn't do anything wrong - but because of how the shared configs worked they got a duplicate report!! This is a really, really bad DevX for obvious reasons.
Now imagine instead that the configs both configure the same rule which has a bunch of config options - and it's configured in such a way that conflicts with the other rule. For example imagine that the rule is
@typescript-eslint/array-type
and the two configs are:'ts-foo/array-type': ['error', { default: 'array' }]
and
'ts-bar/array-type': ['error', { default: 'generic' }]
When the user lints this code:
They'll get a lint error from
ts-bar/array-type
telling them to use the generic form of the array type.When they convert their code to this:
They'll get a lint error from
ts-foo/array-type
telling them to use the array form of the array type.Another big oof! We have an impossible-to-solve linting situation!
In the eslintrc config world this case was impossible because both configs MUST use the same namespace and thus the configs will cascade and override - i.e. this means that the
ts-bar/
config would "win" because it's the last one listed in the config array.Based on Nick's answer here the answer is "this is all just JS so write JS". I.e. there's no reason a config has to just export a flat config array - it could instead export a function that accepts a mapping to allow the user to configure the configuration. EG we could redefine the above configs like
This works - but it's a pretty cumbersome for shared configs to manage.
A lot of configs get auto-generated - especially in the case of recommended configs for plugins - and this style converts the generation problem from a "dump a JSON blob" problem to a "generate actual code" problem - which is a lot more difficult. Additionally for hand-crafted configs this does make things a lot more unwieldy due to the need for computed keys everywhere.
One can also imagine cases like eslint-config-prettier that only turns off rules. Right now it's just a config with a bunch of
'off'
configs for rules that may or may not even be turned on (or even have the plugin registered!!!) in the user's config - but in this new world it will need to be converted to the function form.This is complicated to solve though because, for example, the config disables rules from
eslint-plugin-standard
. If the user doesn't have this plugin then the user shouldn't need to add a mapping for this. Now this is additional logic the config needs to handle where before it didn't!I guess my big concern here is that this new system pushes a lot of complexity onto the users and ecosystem to solve. The ecosystem needs to decide how it solves the problem of plugin namespacing and the users need to be aware of this problem, consume shared configs correctly, and know how to resolve issues if a config doesn't "play nice".
I can't help but think that there has to be a better way though where ESLint could leverage some of its internal knowledge about things to help. For example the plugins all export a
meta
field to help uniquely identify them - perhaps ESLint can leverage this information to deduplicate things.For example - perhaps there could be a way for a user to register a "canonical namespace" for each plugin in their config as a way to enforce that when ESLint resolves their config they end up with exactly one namespace per plugin.
For example something like:
This would allow the user to decide exactly what name they want to use in their project without worry about how the shared configs are designed.
I do realise that being able to duplicate a plugin and have multiple copies of a rule may be a desirable situation in some rarer cases - but I believe that in the vast, vast majority of cases users would not want duplication.
It would be straightforward for a user to duplicate a plugin for this case by changing the plugin's
meta
(egconst pluginCopy = {...plugin, meta: {...plugin.meta, name: "new name"}}
).Beta Was this translation helpful? Give feedback.
All reactions