From 88de0b2159900afbeb4f5a0287df32f1853b233e Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 02:05:50 -0500 Subject: [PATCH 01/14] New: Load configs and plugins relative to where they're referenced --- .../2018-relative-package-loading/README.md | 237 ++++++++++++++++++ .../config-tree-example.png | Bin 0 -> 56528 bytes 2 files changed, 237 insertions(+) create mode 100644 designs/2018-relative-package-loading/README.md create mode 100644 designs/2018-relative-package-loading/config-tree-example.png diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md new file mode 100644 index 00000000..9d5bc48b --- /dev/null +++ b/designs/2018-relative-package-loading/README.md @@ -0,0 +1,237 @@ +- Start Date: 2018-12-03 +- RFC PR: (leave this empty, to be filled in later) +- Authors: Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark)) + +# Load configs and plugins relative to where they're referenced + +## Summary + +This feature would update ESLint to plugins and shareable configs relative to the location of the config files where they're referenced, rather than relative to the location of the running ESLint instance. As a result, configs would be able to specify plugins as their own dependencies, and ESLint users would see fewer confusing errors about missing plugins. + +## Motivation + +Currently, ESLint plugins and shareable configs are loaded from the location of the ESLint package, rather than the location of the config file where the plugins and configs are referenced. This leads to several problems: + +* The current behavior requires end users to manually install any plugins required by a shareable config. As a result, a shareable config can't add a plugin without requiring new manual installation steps from the end user. This greatly decreases the ergonomics of using custom rules from plugins in shareable configs, and results in increased pressure to add new rules and options to ESLint core. +* The current behavior assumes that if a user installs a config/plugin and ESLint in the same project, then ESLint will be able to load that config/plugin. This relies on an implementation detail of how npm works rather than a specified behavior, which leads to problems when using other package management strategies, e.g. with `lerna`. (More details about this problem can be found in [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125).) It also creates problems when using ESLint as a non-top-level dependency with package management strategies that strictly enforce dependency validity with Yarn Plug 'n Play. +* The current behavior leads to a large amount of confusion from users where ESLint behaves differently depending on whether it's installed "globally" with npm. + +A separate but related facet of ESLint's design currently requires plugin names to be globally unique, leading to some additional problems: + +* If two shareable configs depend on two different versions of the same plugin, the configs are entirely incompatible and can't be used together. +* It's difficult to create features that load plugins from other locations (e.g. as proposed in [eslint/eslint#6237](https://github.com/eslint/eslint/issues/6237)), because the name of a plugin loaded from another location might conflict with the name of a plugin in `node_modules`. + +### Design goals of solution + +* A config author should be able to add or upgrade any plugin in their config, without requiring additional installation steps from end users that extend that config. +* The end user should maintain the ability to override any configuration setting inherited from an extended config. +* A config author should be able to extend any two other configs at the same time, and have ESLint lint their code successfully. (The two configs might advocate mutually-incompatible code styles, but that issue is out of scope for this proposal.) +* ESLint's config-loading behavior should be compatible with the use of any package manager that follows the de-facto `package.json` spec, without relying on the implementation details of any particular package manager. +* Standalone config files should continue to be usable as shareable configs, and vice versa, without any changes. +* Shareable configs which currently have plugins as `peerDependencies` should be able to transition to the new solution without requiring changes to the configs of their users (or at least the vast majority of their users). +* The vast majority of existing configs in local-installation setups should continue to work with the new solution. + +## Detailed Design + +ESLint should resolve shareable configs and plugins relative to the config file that loads them. In other words, if a config file specifies `extends: ["foo"]` or `plugins: ["bar"]`, then the appropriate shareable config will be resolved in the same manner as if `require("eslint-config-foo")` or `require("eslint-plugin-bar")` had been called from the same location as that config file. + +This change creates some complications in certain corner cases. These complications, and a proposed solution for them, are described below. + +### The duplicate-plugin problem + +Implementing this scheme as-is would cause naming ambiguity when referring to rules. For example, there could be two shareable configs which have dependencies on two different plugins that both happen to be called `react` (or they depend on two different versions of a plugin called `react`). If the end user depended on these two shareable configs and also configured a rule like `react/some-rule` in their top-level config, the end user's config would be ambiguous because it wouldn't be clear which `react` plugin they were referring to. Since the configurations for a given rule might be incompatible across different versions of a plugin, this could make it impossible to set the configuration for a particular rule or to override a configuration which was set by a shareable config. This would be an unacceptably poor user experience. + +Another way to state the problem is that ESLint's mechanism for naming rules in a config file (`plugin-name/rule-name`) is fundamentally unable to disambiguate two plugins that have the same name. Currently, there are no naming conflicts because all plugins are loaded from the location of the `eslint` package, so plugins effectively live in a global namespace. If plugins could be loaded as dependencies of shareable configs, as implied by this proposal, then naming conflicts would start to become a problem. + +Note: The name ambiguity problem applies to all named resources provided by a plugin. This includes rules and environments, and will also include other resources such as processors if [RFC #3](https://github.com/eslint/rfcs/pull/3) is approved. For brevity, the rest of this RFC only refers to rule naming, but the same solution will also be used to name other plugin-provided resources. + +### Solving the duplicate-plugin problem + +Since the problem stems from a limitation of ESLint's naming scheme, the most straightforward solution to the problem would involve changing the way that rules are named in a config file. Specifically, rules will now be addressed with hierarchical naming for rules, where rules can be configured with something that looks like a path. (In other words, a user could refer to a rule like `foo::react/some-rule` for the version of `eslint-plugin-react` used by `eslint-config-foo`, and this would be a different rule than `bar::react/some-rule`, which would refer to the version of `eslint-plugin-react` used by `eslint-config-bar`.) To preserve compatibility, the existing naming scheme `react/some-rule` would continue to work in cases where the reference is unambiguous. + +The following sections describe the specifics of how hierarchical naming works. + +#### Config trees + +When describing how rule name resolution works in this proposal, it's useful to think of a "config tree" representing the dependencies between shareable configs and plugins. The root node is the end user's config, and each node has a set of named children representing the shareable configs that it extends and plugins that it depends on. Here's an example tree: + +![](./config-tree-example.png) + +In this example, the end user's config extends `eslint-config-foo` and `eslint-config-bar`. `eslint-config-bar` extends `eslint-config-baz`. `eslint-config-foo` and `eslint-config-baz` both depend on versions of `eslint-plugin-react` (perhaps different versions, although this doesn't matter as far as resolution is concerned). `eslint-config-baz` also depends on `eslint-plugin-import`. + +#### Details of hierarchical rule name resolution + +* Each reference to a plugin rule in a config consists of three parts: a *config scope* (i.e. a list of configs), a plugin name, and a rule name. + * For example, in an existing rule configuration like `react/no-typos`, the config scope is an empty list, the plugin name is `react`, and the rule name is `no-typos`. (In existing rule configurations, the config scope is always an empty list.) + * In a rule configuration like `foo::bar::react/no-typos`, the config scope is `['foo', 'bar']`, the plugin name is `react`, and the rule name is `no-typos`. + * The syntax shown here for writing a config scope (which uses `::` as a separator) is only a minor detail of this proposal, and is open to bikeshedding. For the purposes of understanding this proposal, it's recommended to focus on the abstract idea of a `(configScope, pluginName, ruleName)` triple; the question of how best to syntactically represent that idea can be decided independently of the rest of the proposal. +* Each reference to a plugin rule is also implicitly associated with a config in the config tree. References that appear in a config file are associated with that config file. References outside of a config file (e.g. from the command line or inline config comments) are associated with the root of the config tree. + +To resolve a `(configScope, pluginName, ruleName)` triple to a loaded rule, which is referenced in a config `baseConfig`: + +* If `configScope` is non-empty, find the child config of `baseConfig` in the config tree which has a name of `configScope[0]`, and recursively resolve the rule `(configScope.slice(1), pluginName, ruleName)` from that config. + * (If there is no such child config, the rule reference is invalid. ESLint should exit with a useful error message.) +* Otherwise, if `configScope` is empty: + * If `baseConfig` has a direct child plugin with the name `pluginName`, or `baseConfig` is a plugin config from a plugin called `pluginName`, return the rule called `ruleName` from that plugin. + * Otherwise, search for all plugins that are descendants of `baseConfig` in the config tree and have a name of `pluginName`. + * If there is exactly one such plugin, return the rule called `ruleName` from that plugin. + * If there are no such plugins, the rule reference is invalid. ESLint should exit with a useful error message. + * If there is more than one such plugin, the rule reference is ambiguous. ESLint should exit with useful error message. + * For example, this error message could include all of the matching plugins that were found, and provide a replacement rule reference that would disambiguate each of them. The user could the choose one of the replacements and copy-paste it into their config. This would make it simple for the user to resolve an ambiguity. + +ESLint uses config scopes relative to the root of the config tree when creating rule IDs. It uses the minimum number of scopes necessary to unambiguously refer to a rule using the resolution steps below. For example, if only one version of `eslint-plugin-react` is present, ESLint might generate a rule ID of `react/no-typos`; if this reference is ambiguous, ESLint might generate a rule ID of `foo::react/no-typos`. In all cases, the rule ID of a rule is also a valid reference to that rule from the end user's config file. + +#### Examples of hierarchical rule name resolution + + +Using the config tree given above (reproduced below for convenience): + +
+Example config tree (same as above) + +![](./config-tree-example.png) +
+ +* If the end user's config references the rule `react/no-typos`, the config scope is empty. Since the root node of the tree has multiple descendants called `eslint-plugin-react`, the rule reference is ambiguous. +* If the end user's config references the rule `bar::react/no-typos`, the config scope is non-empty, so the resolution strategy then tries to resolve the rule `react/no-typos` from the `eslint-config-bar` node in the tree. Since there is only one descendent of that node called `eslint-plugin-react`, the rule would successfully resolve to the `no-typos` rule of that plugin. + +### Notable properties of this design + +* This strategy allows shareable configs to specify plugins and other shareable configs as direct dependencies, without manual installation steps by the user. It also ensures that the end user can always override any extended configuration. +* With hierarchial rule naming, any ambiguity in a rule reference in a given config file will be immediately apparent to the author of that config file, since the presence of an ambiguity only depends on the descendants of that config in the config tree. In other words, there is no situation where a particular user's configuration would be broken and they would need to lobby the author of their shareable config to make a change (because in that case, the shareable config would be broken for all of its users and likely would have been fixed before publishing). +* Hierarchial rule naming is mostly backwards-compatible with existing setups, because in existing setups there is always at most one version of a plugin reachable from anywhere in a config tree. Some exceptions to this are described in the "Backwards Compatibility Analysis" section. +* Adding a plugin to a shareable config is a breaking change for the shareable config, because it creates the possibility of an ambiguity in ancestor configs. However, note that: + * Adding a plugin to a shareable config would usually be a breaking change for other reasons anyway, because the shareable config would be typically enable new rules from that plugin, causing more errors to be reported to the end user. + * With the current status quo, adding a plugin to a shareable config is always a breaking change because the end user needs to manually install it. +* With this proposal, there is no longer any behavioral distinction between a "local" and a "global" installation of ESLint. Since packages are no longer loaded from the location of the ESLint package, ESLint generally behaves the same way regardless of where it's installed. (As a minor caveat, ESLint still resolves references to core rules by using the rules that are bundled with it, so if a user has different *versions* of ESLint installed globally and locally, the behavior might still vary depending on which version of ESLint is run.) +* For configs loaded via `extends` with a relative path, via `.eslintrc.*` files in nested folders, the location of the "base" config file is used to load plugins and shareable configs. This is a minor detail to address the unusual case where a relative `extends` clause crosses a `node_modules` boundary, which would otherwise allow the same plugin name to resolve to two different plugins without any shareable config reference that could be used to disambiguate them. + +## Documentation + +This proposal has a few backwards-incompatible aspects, so it would appear in a migration guide for the major version where it's introduced. It would also entail updating plugin documentation to suggest adding shareable configs as dependencies, and removing documentation about the difference between local and global ESLint installations. + +Importantly, **it is *not* necessary for users to understand the details of hierarchial name resolution**. This should decrease cognitive load for users, and reduce the amount of documentation they need to read in order to create a config. + +To see why this is the case, consider three common usage scenarios: + +* A user sees a report from a rule, and decides to disable that rule. + +Since ESLint includes necessary config scopes in rule IDs, a report from a rule will already identify the name needed for the end user to configure that rule. For example, if a `react/no-typos` rule reports an error, and there are multiple versions of `eslint-plugin-react` loaded, the error might be displayed to the user as coming from `airbnb::react/no-typos`. Then the user can simply copy-paste that name into their config and disable it with something like `{ rules: { "airbnb::react/no-typos": "off" } }`, even if they don't fully understand where `airbnb::` came from. (If there is only one version of `eslint-plugin-react` loaded, the rule ID will simply be `react/no-typos`, and the user will be able to configure the rule as `react/no-typos` in the same manner as today.) + +* A user wants to enable a rule from a plugin + +In this case, the user can simply install the plugin themselves as a devDependency and configure its rules normally (e.g. with `{ plugins: ["react"], rules: { "react/no-typos": "error" }`), which will work regardless of what other configs the user is using. + +* A user extends a shareable config (`eslint-config-foo`), and later on they want to extend another shareable config (`eslint-config-bar`) which uses the same plugin (`eslint-plugin-react`) + +In this case, the user might already have a config disabling some plugin rules, e.g. `{ rules: { "react/no-typos": "off" }, "extends": ["foo"] }`. If they add `bar` to the `extends` list, then ESLint will report an error looking something like this: + +> The reference to the `react/no-typos` rule in `` is ambiguous. Did you mean: +> * `foo::react/no-typos` (for the version of `eslint-plugin-react` from `eslint-config-foo`)? +> * `bar::react/no-typos` (for the version of `eslint-plugin-react` from `eslint-config-bar`)? + +Then the user could select a rule (or perhaps both rules) based on the provided explanation and disable it in their config by copy-pasting the given rule ID. + +## Drawbacks + +### Users may occasionally see duplicate reports + +Suppose a user extends `eslint-config-foo` and `eslint-config-bar`, which both happen to depend on `eslint-plugin-react`. Additionally, suppose `eslint-config-foo` and `eslint-config-bar` each enable the `no-typos` rule from their respective versions of `eslint-plugin-react`. + +This will have the effect of configuring two independent rules, `foo::react/no-typos` and `bar::react/no-typos`. The behavior of the `react/no-typos` rule is likely to be quite similar between different versions of `eslint-plugin-react` (and the behavior will be identical if the two shareable configs are using the same version). As a result, violations of a coding style might result in two linting errors for the same problem: one from `foo::react/no-typos`, and one from `bar::react/no-typos`, resulting in a suboptimal user experience. + +Duplicate reports seem to be a necessary side-effect of the goal to allow different plugins with the same name to be configured simultaneously. For example, if `eslint-config-foo` and `eslint-config-bar` had different configs for `react/no-typos`, then running both rules simultaneously might be a desirable behavior. (Consider a rule like [`no-restricted-syntax`](https://eslint.org/docs/rules/no-restricted-syntax), which can report entirely different code patterns depending on configuration.) Even if `eslint-config-foo` and `eslint-config-bar` used the same configuration for `react/no-typos`, running both versions of the rule simultaneously might still be desirable if the different versions of `eslint-plugin-react` had substantially different behaviors for the `no-typos` rule. + +In the case where `eslint-config-foo` and `eslint-config-bar` both end up loading the exact same copy of `eslint-plugin-react` (e.g. due to a package manager's tree flattening), ESLint could avoid the performance cost of running the same rule twice by simply running the rule once and producing two reports. (It doesn't seem like a good idea to only output one report in this case, because this would make ESLint's output highly dependent on how a package manager decides to arrange packages in `node_modules`.) + +The UI impact of this problem could largely be mitigated by updating formatters to deduplicate reports when displaying them. For example, rather than displaying two separate lines for two reports with the same message at the same location, a formatter could display the message once and list both rule names. + +### Shareable configs can no longer reconfigure their siblings' plugins + +If a user extends `eslint-config-foo` and `eslint-config-bar`, and `eslint-config-foo` depends on `eslint-plugin-react`, then `eslint-config-bar` cannot reference the version of `eslint-plugin-react` used by `eslint-config-foo`. (If `eslint-config-bar` wanted to use `eslint-plugin-react`, it could instead install its own version of `eslint-plugin-react`, independently of `eslint-config-foo`.) + +This property is by design; since `eslint-config-bar` can't know what version of `eslint-plugin-react` is used by its siblings (or if `eslint-plugin-react` is in use at all), it generally wouldn't know how to produce a reasonable configuration for the rules in `eslint-plugin-react`. However, this will break some aspects of shareable configs like [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier), which attempts to disable all stylistic rules from a set of a few popular plugins. (The particular issue of disabling stylistic rules might be solvable separately from this proposal since rules can now be explicitly marked as stylistic through the `type` metadata flag.) + +### The distinction between direct-dependency plugins and plugins from shareable configs may be confusing + +If a user installs `eslint-plugin-react` themselves, and also depends on `eslint-config-foo` which depends on `eslint-plugin-react`, then the following two configs have slightly different meanings: + +```json +{ + "rules": { + "react/no-typos": "error" + }, + "extends": ["foo"] +} +``` + +```json +{ + "rules": { + "react/no-typos": "error" + }, + "extends": ["foo"], + "plugins": ["react"] +} +``` + +In the first case, only one version of `eslint-plugin-react` is loaded (the version from `eslint-config-foo`), so the `react/no-typos` reference unambiguously refers to that version of `eslint-plugin-react`. + +In the second case, there are two versions of `eslint-plugin-react` loaded: one from `eslint-config-foo`, and one from the end user's config. However, no ambiguity error is raised because the latter is a *direct dependency* of the end user's config. Instead, the reference `react/no-typos` resolves to the user's version of `eslint-plugin-react`. This has the potential to be confusing. + +This problem arises due to the special exception in the rule name resolution algorithm where if the `configScope` is empty and the given plugin name is available as a direct dependency, then the rule reference always resolves to that dependency without doing the normal ambiguity checks. The exception exists because if such a reference was considered ambiguous, it would not be possible to disambiguate it by specifying a scope. + +In other words, in this case it would be possible to refer to the version of the plugin from `eslint-config-foo` with `foo::react/no-typos`, but there isn't anything that could be used in place of `foo::` if the user intended to refer to the direct dependency. Instead, the unscoped `react/no-typos` reference is allowed and refers to the direct dependency rather than being considered ambiguous. + +One potential alternative would be to introduce a separate syntax to refer to direct dependencies of the current config file. For example, `::react/no-typos` (only a prefix of `::` with nothing before it) could refer to the direct dependency, and `foo::react/no-typos` could refer to the version from `eslint-config-foo`. The drawbacks of this alternative would be an increase in complexity, a loss of the invariant that a user can always use `pluginName/ruleName` to refer to plugins that they directly depend on, regardless of other things they have installed. + +As a sidenote, this example demonstrates the presence of the `plugins` field in a config is now significant, whereas previously it could sometimes be omitted without changing anything if a plugin was already being loaded from somewhere else. + +### Home-directory configs which depend on shareable configs will usually stop working + +With this change, config files in a home directory will attempt to load their shareable configs from the home directory, usually resulting in an error. (Previously, these home-directory configs would implicitly depend on having certain plugins available from whatever version of ESLint was being used to load them.) Given this constraint, we might consider simply dropping support for home-directory configs, as has been discussed separately from this proposal in the past. + +### Users are exposed to some details about the structure of their shareable configs + +With hierarchial name resolution, an end user would be exposed to some details of the layout of shareable configs that they depend on. For example, if a shareable config `eslint-config-foo` has two descendant plugins with the same name, then the config scope that is needed to refer to those rules is "contagious". In other words, if `eslint-config-foo` needs to use a reference like `bar::react/no-typos` to avoid ambiguities, then a config that extends `eslint-config-foo` needs to use something like `foo::bar::react/no-typos` to configure that rule. + +I consider this to be acceptable because end users are already exposed to many of the details of their shareable configs, in that changes to shareable configs will lead to different linting errors/warnings being reported on the end user's code. From an encapsulation perspective, it seems important to protect configs from caring about details of *sibling* shareable configs, since this would create implicit dependencies between the sibling configs where no dependency otherwise existed. However, it seems less important to protect configs from details of *descendent* shareable configs, since there is already a dependency relationship between them anyway. + +Additionally, the problem of exposure to details of dependency config appears to be inherent to any solution that both (a) gives users the power to arbitrarily override third-party configuration, and (b) allows third-party configuration to pull in custom rules from multiple external sources. + +### Hierarchial rule name resolution adds complexity + +Hierarchial rule name resolution would increase the complexity of how configs are resolved, potentially adding a maintenance burden. (However, the implementation would also provide a good opportunity to refactor the existing config-loading code, which might make that code easier to maintain overall.) + +## Backwards Compatibility Analysis + +This proposal maintains compatibility for most shareable configs, and most local installation setups from end users. There are a few backwards-compatible parts: + +* Most "global installation" setups that use plugins will need to be modified. Previously, a user need to install plugins globally when ESLint was installed globally. With this proposal implemented, a user should install plugins as a dependency of the codebase that contains their config file. +* Configs can no longer rely on plugin names being globally unique. For example, if two configs independently configure a plugin with the same name, their configurations would previously override each other; with this proposal implemented, the configs will create two independent configurations. Along similar lines, shareable configs can no longer reconfigure their siblings' plugins, as described in the "Drawbacks" section. +* Previously, a config could use something like `extends: ['plugin:foo/bar']` without explicitly declaring a dependency on the `eslint-plugin-foo` plugin. With this change, such a config would be required to also add `plugins: ['foo']` for ESLint to load `eslint-plugin-foo`. + +## Alternatives + +* One alternative solution would be to avoid the complexity of hierarchical rule name resolution by simply raising a fatal error if two plugins have the same name. That solution is much simpler than this one, and avoids the duplicate report problem. However, with that solution the user has little recourse if two shareable configs both depend on the same plugin, resulting in a "dependency hell" scenario where many pairs of shareable configs would be incompatible with each other due to different dependency versions. +* [eslint/eslint#3458 (comment)](https://github.com/eslint/eslint/issues/3458#issuecomment-257161846) proposed solving the duplicate-name problem by using plugins that depend on other plugins and reexport the rules of their dependencies, without any changes to ESLint core. It suggested two possible ways of re-exporting the rules: either a plugin could export them directly with the same name, or it could give the names a common prefix. This was proposed to address the issue that end users are exposed to their configs' dependencies. + + Unfortunately, this solution has a few downsides that prevented it from being widely adopted: + + * It would require everyone using shareable configs to switch to using plugins, which was regarded by some config authors as an unacceptably large breaking change. + * It could create confusion about where a rule came from (and where bugs should be reported), because a rule might have been passed through many different plugins before reaching the user. + * It still caused issues if two loaded plugins exported rules with the same name, resulting in either (a) a naming conflict where one rule would be unconfigurable, or (b) scoped rule names where the end user would end up exposed to their config's dependencies anyway. + * This solution would not fix the issue that ESLint depends on package managers' implementation details (at least partly because that issue was not known at the time that the solution was proposed). +* A final alternative would be to do nothing. This would avoid all compatibility impact, but also leave ESLint unable to be used in certain package management setups, and users would likely continue to be confused about why their plugins sometimes aren't found. + +## Open Questions + +* How should users refer to direct-dependency plugins? (See the section titled, "The distinction between direct-dependency plugins and plugins from shareable configs may be confusing".) + +## Related Discussions + +* [eslint/eslint#3458](https://github.com/eslint/eslint/issues/3458) +* [eslint/eslint#6732](https://github.com/eslint/eslint/issues/6732) +* [eslint/eslint#9897](https://github.com/eslint/eslint/issues/9897) +* [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125) +* [eslint/eslint#10643](https://github.com/eslint/eslint/issues/10643) diff --git a/designs/2018-relative-package-loading/config-tree-example.png b/designs/2018-relative-package-loading/config-tree-example.png new file mode 100644 index 0000000000000000000000000000000000000000..6b53c7d67aa13232bb8572483c991e286ee5c3ec GIT binary patch literal 56528 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 E0Pj=*(*OVf literal 0 HcmV?d00001 From a043b9d30abc9970efb6406d97e4ae4dccf3c05c Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Mon, 3 Dec 2018 11:42:57 -0500 Subject: [PATCH 02/14] Update designs/2018-relative-package-loading/README.md Co-Authored-By: not-an-aardvark --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 9d5bc48b..605a99ea 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -101,7 +101,7 @@ Using the config tree given above (reproduced below for convenience): * With hierarchial rule naming, any ambiguity in a rule reference in a given config file will be immediately apparent to the author of that config file, since the presence of an ambiguity only depends on the descendants of that config in the config tree. In other words, there is no situation where a particular user's configuration would be broken and they would need to lobby the author of their shareable config to make a change (because in that case, the shareable config would be broken for all of its users and likely would have been fixed before publishing). * Hierarchial rule naming is mostly backwards-compatible with existing setups, because in existing setups there is always at most one version of a plugin reachable from anywhere in a config tree. Some exceptions to this are described in the "Backwards Compatibility Analysis" section. * Adding a plugin to a shareable config is a breaking change for the shareable config, because it creates the possibility of an ambiguity in ancestor configs. However, note that: - * Adding a plugin to a shareable config would usually be a breaking change for other reasons anyway, because the shareable config would be typically enable new rules from that plugin, causing more errors to be reported to the end user. + * Adding a plugin to a shareable config would usually be a breaking change for other reasons anyway, because the shareable config would typically be added in order to enable new rules from that plugin, causing more errors to be reported to the end user. * With the current status quo, adding a plugin to a shareable config is always a breaking change because the end user needs to manually install it. * With this proposal, there is no longer any behavioral distinction between a "local" and a "global" installation of ESLint. Since packages are no longer loaded from the location of the ESLint package, ESLint generally behaves the same way regardless of where it's installed. (As a minor caveat, ESLint still resolves references to core rules by using the rules that are bundled with it, so if a user has different *versions* of ESLint installed globally and locally, the behavior might still vary depending on which version of ESLint is run.) * For configs loaded via `extends` with a relative path, via `.eslintrc.*` files in nested folders, the location of the "base" config file is used to load plugins and shareable configs. This is a minor detail to address the unusual case where a relative `extends` clause crosses a `node_modules` boundary, which would otherwise allow the same plugin name to resolve to two different plugins without any shareable config reference that could be used to disambiguate them. From 282edacfd2ba0b835a767d391e6cc420273860f6 Mon Sep 17 00:00:00 2001 From: Kevin Partington Date: Mon, 3 Dec 2018 11:45:13 -0500 Subject: [PATCH 03/14] Update designs/2018-relative-package-loading/README.md Co-Authored-By: not-an-aardvark --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 605a99ea..dabf1912 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -183,7 +183,7 @@ This problem arises due to the special exception in the rule name resolution alg In other words, in this case it would be possible to refer to the version of the plugin from `eslint-config-foo` with `foo::react/no-typos`, but there isn't anything that could be used in place of `foo::` if the user intended to refer to the direct dependency. Instead, the unscoped `react/no-typos` reference is allowed and refers to the direct dependency rather than being considered ambiguous. -One potential alternative would be to introduce a separate syntax to refer to direct dependencies of the current config file. For example, `::react/no-typos` (only a prefix of `::` with nothing before it) could refer to the direct dependency, and `foo::react/no-typos` could refer to the version from `eslint-config-foo`. The drawbacks of this alternative would be an increase in complexity, a loss of the invariant that a user can always use `pluginName/ruleName` to refer to plugins that they directly depend on, regardless of other things they have installed. +One potential alternative would be to introduce a separate syntax to refer to direct dependencies of the current config file. For example, `::react/no-typos` (only a prefix of `::` with nothing before it) could refer to the direct dependency, and `foo::react/no-typos` could refer to the version from `eslint-config-foo`. The drawbacks of this alternative would be an increase in complexity and a loss of the invariant that a user can always use `pluginName/ruleName` to refer to plugins that they directly depend on, regardless of other things they have installed. As a sidenote, this example demonstrates the presence of the `plugins` field in a config is now significant, whereas previously it could sometimes be omitted without changing anything if a plugin was already being loaded from somewhere else. From a1a271f00968cfe72eaab38272791722677cd3b4 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 13:09:37 -0500 Subject: [PATCH 04/14] Apply suggested changes --- designs/2018-relative-package-loading/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index dabf1912..fcf556e2 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -114,15 +114,17 @@ Importantly, **it is *not* necessary for users to understand the details of hier To see why this is the case, consider three common usage scenarios: -* A user sees a report from a rule, and decides to disable that rule. +### A user sees a report from a rule, and decides to disable that rule. Since ESLint includes necessary config scopes in rule IDs, a report from a rule will already identify the name needed for the end user to configure that rule. For example, if a `react/no-typos` rule reports an error, and there are multiple versions of `eslint-plugin-react` loaded, the error might be displayed to the user as coming from `airbnb::react/no-typos`. Then the user can simply copy-paste that name into their config and disable it with something like `{ rules: { "airbnb::react/no-typos": "off" } }`, even if they don't fully understand where `airbnb::` came from. (If there is only one version of `eslint-plugin-react` loaded, the rule ID will simply be `react/no-typos`, and the user will be able to configure the rule as `react/no-typos` in the same manner as today.) -* A user wants to enable a rule from a plugin +### A user wants to enable a rule from a plugin In this case, the user can simply install the plugin themselves as a devDependency and configure its rules normally (e.g. with `{ plugins: ["react"], rules: { "react/no-typos": "error" }`), which will work regardless of what other configs the user is using. -* A user extends a shareable config (`eslint-config-foo`), and later on they want to extend another shareable config (`eslint-config-bar`) which uses the same plugin (`eslint-plugin-react`) +### A user wants to configure two shareable configs using the same plugin + +Suppose a user extends a shareable config (`eslint-config-foo`), and later on they want to extend another shareable config (`eslint-config-bar`) which uses the same plugin (`eslint-plugin-react`). In this case, the user might already have a config disabling some plugin rules, e.g. `{ rules: { "react/no-typos": "off" }, "extends": ["foo"] }`. If they add `bar` to the `extends` list, then ESLint will report an error looking something like this: @@ -205,7 +207,7 @@ Hierarchial rule name resolution would increase the complexity of how configs ar ## Backwards Compatibility Analysis -This proposal maintains compatibility for most shareable configs, and most local installation setups from end users. There are a few backwards-compatible parts: +This proposal maintains compatibility for most shareable configs, and most local installation setups from end users. There are a few backwards-incompatible parts: * Most "global installation" setups that use plugins will need to be modified. Previously, a user need to install plugins globally when ESLint was installed globally. With this proposal implemented, a user should install plugins as a dependency of the codebase that contains their config file. * Configs can no longer rely on plugin names being globally unique. For example, if two configs independently configure a plugin with the same name, their configurations would previously override each other; with this proposal implemented, the configs will create two independent configurations. Along similar lines, shareable configs can no longer reconfigure their siblings' plugins, as described in the "Drawbacks" section. From af5aeeb125cef78022feda33904014f1782f7221 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 13:40:33 -0500 Subject: [PATCH 05/14] Explain why foo::bar::react/no-typos instead of foo/bar/react/no-typos --- designs/2018-relative-package-loading/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index fcf556e2..1ec3cc42 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -65,6 +65,7 @@ In this example, the end user's config extends `eslint-config-foo` and `eslint-c * For example, in an existing rule configuration like `react/no-typos`, the config scope is an empty list, the plugin name is `react`, and the rule name is `no-typos`. (In existing rule configurations, the config scope is always an empty list.) * In a rule configuration like `foo::bar::react/no-typos`, the config scope is `['foo', 'bar']`, the plugin name is `react`, and the rule name is `no-typos`. * The syntax shown here for writing a config scope (which uses `::` as a separator) is only a minor detail of this proposal, and is open to bikeshedding. For the purposes of understanding this proposal, it's recommended to focus on the abstract idea of a `(configScope, pluginName, ruleName)` triple; the question of how best to syntactically represent that idea can be decided independently of the rest of the proposal. + * Note: Another syntax was considered which uses `/` characters for all separators, e.g. `foo/bar/react/no-typos`. However, this would lead to parsing ambiguity because some rule names have slashes in them, so it would be unclear whether the reference refers to a `react` plugin with a `no-typos` rule, or a `bar` plugin with a `react/no-typos` rule. * Each reference to a plugin rule is also implicitly associated with a config in the config tree. References that appear in a config file are associated with that config file. References outside of a config file (e.g. from the command line or inline config comments) are associated with the root of the config tree. To resolve a `(configScope, pluginName, ruleName)` triple to a loaded rule, which is referenced in a config `baseConfig`: From d2ea03428041374fcd76f5be4d78e696e399d22c Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 14:17:54 -0500 Subject: [PATCH 06/14] Add postinstall scripts as an alternative/explain why they have problems --- .../2018-relative-package-loading/README.md | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 1ec3cc42..afd3fcd8 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -216,16 +216,37 @@ This proposal maintains compatibility for most shareable configs, and most local ## Alternatives -* One alternative solution would be to avoid the complexity of hierarchical rule name resolution by simply raising a fatal error if two plugins have the same name. That solution is much simpler than this one, and avoids the duplicate report problem. However, with that solution the user has little recourse if two shareable configs both depend on the same plugin, resulting in a "dependency hell" scenario where many pairs of shareable configs would be incompatible with each other due to different dependency versions. -* [eslint/eslint#3458 (comment)](https://github.com/eslint/eslint/issues/3458#issuecomment-257161846) proposed solving the duplicate-name problem by using plugins that depend on other plugins and reexport the rules of their dependencies, without any changes to ESLint core. It suggested two possible ways of re-exporting the rules: either a plugin could export them directly with the same name, or it could give the names a common prefix. This was proposed to address the issue that end users are exposed to their configs' dependencies. +### Raise a fatal error for duplciate plugin names - Unfortunately, this solution has a few downsides that prevented it from being widely adopted: +One alternative solution would be to avoid the complexity of hierarchical rule name resolution by simply raising a fatal error if two plugins have the same name. That solution is much simpler than this one, and avoids the duplicate report problem. Notably, implementing that solution first would also allow hierarchical rule name resolution to be added on later if necessary, without breaking compatibility. - * It would require everyone using shareable configs to switch to using plugins, which was regarded by some config authors as an unacceptably large breaking change. - * It could create confusion about where a rule came from (and where bugs should be reported), because a rule might have been passed through many different plugins before reaching the user. - * It still caused issues if two loaded plugins exported rules with the same name, resulting in either (a) a naming conflict where one rule would be unconfigurable, or (b) scoped rule names where the end user would end up exposed to their config's dependencies anyway. - * This solution would not fix the issue that ESLint depends on package managers' implementation details (at least partly because that issue was not known at the time that the solution was proposed). -* A final alternative would be to do nothing. This would avoid all compatibility impact, but also leave ESLint unable to be used in certain package management setups, and users would likely continue to be confused about why their plugins sometimes aren't found. +However, with that solution the user has little recourse if two shareable configs both depend on the same plugin, resulting in a "dependency hell" scenario where many pairs of shareable configs would be incompatible with each other due to different dependency versions. + +### Use plugins that pull rules from other plugins, instead of shareable configs + +[eslint/eslint#3458 (comment)](https://github.com/eslint/eslint/issues/3458#issuecomment-257161846) proposed solving the duplicate-name problem by using plugins that depend on other plugins and reexport the rules of their dependencies, without any changes to ESLint core. It suggested two possible ways of re-exporting the rules: either a plugin could export them directly with the same name, or it could give the names a common prefix. This was proposed to address the issue that end users are exposed to their configs' dependencies. + +Unfortunately, this solution has a few downsides that prevented it from being widely adopted: + +* It would require everyone using shareable configs to switch to using plugins, which was regarded by some config authors as an unacceptably large breaking change. +* It could create confusion about where a rule came from (and where bugs should be reported), because a rule might have been passed through many different plugins before reaching the user. +* It still caused issues if two loaded plugins exported rules with the same name, resulting in either (a) a naming conflict where one rule would be unconfigurable, or (b) scoped rule names where the end user would end up exposed to their config's dependencies anyway. +* This solution would not fix the issue that ESLint depends on package managers' implementation details (at least partly because that issue was not known at the time that the solution was proposed). + +### Recommend that shareable configs install their peer dependencies with a postinstall script + +Another possible course of action would be to encourage shareable configs to install their peer dependencies with a `postinstall` script. This would avoid adding complexity to ESLint core, while preventing users from needing to manually install peer dependencies. + +Unfortunately, this would cause a few issues: + +* If two shareable configs depended on different versions of the same plugin, their postinstall scripts would conflict with each other, resulting in one of the shareable configs having an incompatible peerDependency. This would require end users to manually remove a shareable config, and end users might end up confused about why they can't use two shareable configs together. +* With this solution, ESLint would continue to depend on implementation details of package managers, so it would still break under some valid setups (e.g. in `lerna` monorepos). + +This solution has similar effects to the solution of raising an fatal error for duplicate plugin names. Both avoid the complexity of hierarchical rule name resolution, but they offer no good user recourse if conflicting plugin versions are used. Since the "disallow duplicate plugin names" solution also fixes the bug where ESLint fails with some package management setups, it seems like a better fallback solution than recommending a postinstall script, if hierarchial rule name resolution is determined to add too much complexity. + +### Do nothing + +A final alternative would be to do nothing. This would avoid all compatibility impact, but also leave ESLint unable to be used in certain package management setups, and users would likely continue to be confused about why their plugins sometimes aren't found. ## Open Questions From d0d84744439dc62c3113019e254946ef3ad25c1e Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 14:24:35 -0500 Subject: [PATCH 07/14] Add implementation notes/discussion about Node caching --- designs/2018-relative-package-loading/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index afd3fcd8..eb76485b 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -107,6 +107,23 @@ Using the config tree given above (reproduced below for convenience): * With this proposal, there is no longer any behavioral distinction between a "local" and a "global" installation of ESLint. Since packages are no longer loaded from the location of the ESLint package, ESLint generally behaves the same way regardless of where it's installed. (As a minor caveat, ESLint still resolves references to core rules by using the rules that are bundled with it, so if a user has different *versions* of ESLint installed globally and locally, the behavior might still vary depending on which version of ESLint is run.) * For configs loaded via `extends` with a relative path, via `.eslintrc.*` files in nested folders, the location of the "base" config file is used to load plugins and shareable configs. This is a minor detail to address the unusual case where a relative `extends` clause crosses a `node_modules` boundary, which would otherwise allow the same plugin name to resolve to two different plugins without any shareable config reference that could be used to disambiguate them. +### Implementation notes + +#### Resolving modules + +The task of resolving modules from a particular location will likely be accomplished using Node's `Module._findPath` API, similar to how it's [already used in the codebase](https://github.com/eslint/eslint/blob/62fd2b93448966331db3eb2dfbe4e1273eb032b2/lib/util/module-resolver.js). + +Node's module caching is not expected to pose an issue, because module caching never causes a different version of a package to get loaded. Its only effect is that if the *same* package is loaded from two different sources, both sources might load the same JavaScript object. + +* If two shareable configs depend on different versions of a particular plugin, then two separate versions of the plugin package will be loaded, even though they share a package name. +* If two shareable configs depend on the same version of a particular plugin, then load the plugin from the two locations may or may not create the same JavaScript object, depending on how the package manager flattens packages. This should not cause a problem in either case, because ESLint doesn't mutate the plugin objects that it loads. + +#### Effect on existing codebase + +The bulk of the implementation will likely involve rewriting large portions of [`lib/config/config-file.js`](https://github.com/eslint/eslint/blob/62fd2b93448966331db3eb2dfbe4e1273eb032b2/lib/config/config-file.js) to ensure that it builds a tree of configs as described above, rather than repeatedly merging everything into a single config. + +The implementation is not expected to affect config caching logic; configs can be cached from the filesystem in the same manner that they are today. The implementaiton should only change what happens with a config after it's loaded from the filesystem, regardless of whether the filesystem access was cached. + ## Documentation This proposal has a few backwards-incompatible aspects, so it would appear in a migration guide for the major version where it's introduced. It would also entail updating plugin documentation to suggest adding shareable configs as dependencies, and removing documentation about the difference between local and global ESLint installations. From d62e3e803711b8619c3f2829e9a8f06405dc7d83 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 3 Dec 2018 14:32:12 -0500 Subject: [PATCH 08/14] s/duplciate/duplicate --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index eb76485b..3ac3a42d 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -233,7 +233,7 @@ This proposal maintains compatibility for most shareable configs, and most local ## Alternatives -### Raise a fatal error for duplciate plugin names +### Raise a fatal error for duplicate plugin names One alternative solution would be to avoid the complexity of hierarchical rule name resolution by simply raising a fatal error if two plugins have the same name. That solution is much simpler than this one, and avoids the duplicate report problem. Notably, implementing that solution first would also allow hierarchical rule name resolution to be added on later if necessary, without breaking compatibility. From 6e5ce88bacec9d940f273617401ad3ca6b4f1ce5 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Tue, 4 Dec 2018 12:44:14 -0500 Subject: [PATCH 09/14] Ensure `extends: "plugin:foo/bar"` still loads `foo` for compatibility --- designs/2018-relative-package-loading/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 3ac3a42d..e1e6ba62 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -59,6 +59,8 @@ When describing how rule name resolution works in this proposal, it's useful to In this example, the end user's config extends `eslint-config-foo` and `eslint-config-bar`. `eslint-config-bar` extends `eslint-config-baz`. `eslint-config-foo` and `eslint-config-baz` both depend on versions of `eslint-plugin-react` (perhaps different versions, although this doesn't matter as far as resolution is concerned). `eslint-config-baz` also depends on `eslint-plugin-import`. +In detail, a config is said to depend on a given shareable config if it's listed in the `extends` section. A config is said to depend on a given plugin if either (a) it's listed in the `plugins` section, or (b) one of the plugin's configs is listed in the `extends` section. + #### Details of hierarchical rule name resolution * Each reference to a plugin rule in a config consists of three parts: a *config scope* (i.e. a list of configs), a plugin name, and a rule name. @@ -229,7 +231,6 @@ This proposal maintains compatibility for most shareable configs, and most local * Most "global installation" setups that use plugins will need to be modified. Previously, a user need to install plugins globally when ESLint was installed globally. With this proposal implemented, a user should install plugins as a dependency of the codebase that contains their config file. * Configs can no longer rely on plugin names being globally unique. For example, if two configs independently configure a plugin with the same name, their configurations would previously override each other; with this proposal implemented, the configs will create two independent configurations. Along similar lines, shareable configs can no longer reconfigure their siblings' plugins, as described in the "Drawbacks" section. -* Previously, a config could use something like `extends: ['plugin:foo/bar']` without explicitly declaring a dependency on the `eslint-plugin-foo` plugin. With this change, such a config would be required to also add `plugins: ['foo']` for ESLint to load `eslint-plugin-foo`. ## Alternatives From 2a26810c5e7872befc5e866b0fa361a9b1a92e3c Mon Sep 17 00:00:00 2001 From: Brandon Mills Date: Tue, 4 Dec 2018 14:47:07 -0500 Subject: [PATCH 10/14] Update designs/2018-relative-package-loading/README.md Co-Authored-By: not-an-aardvark --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index e1e6ba62..4dd3cd4f 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -6,7 +6,7 @@ ## Summary -This feature would update ESLint to plugins and shareable configs relative to the location of the config files where they're referenced, rather than relative to the location of the running ESLint instance. As a result, configs would be able to specify plugins as their own dependencies, and ESLint users would see fewer confusing errors about missing plugins. +This feature would update ESLint to load plugins and shareable configs relative to the location of the config files where they're referenced, rather than relative to the location of the running ESLint instance. As a result, configs would be able to specify plugins as their own dependencies, and ESLint users would see fewer confusing errors about missing plugins. ## Motivation From 55659901143fb933c3baa411bf6f8071730172d9 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Tue, 4 Dec 2018 19:09:20 -0500 Subject: [PATCH 11/14] Add example where a plugin config appears as part of a config scope --- designs/2018-relative-package-loading/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 4dd3cd4f..8b0f058e 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -97,6 +97,9 @@ Using the config tree given above (reproduced below for convenience): * If the end user's config references the rule `react/no-typos`, the config scope is empty. Since the root node of the tree has multiple descendants called `eslint-plugin-react`, the rule reference is ambiguous. * If the end user's config references the rule `bar::react/no-typos`, the config scope is non-empty, so the resolution strategy then tries to resolve the rule `react/no-typos` from the `eslint-config-bar` node in the tree. Since there is only one descendent of that node called `eslint-plugin-react`, the rule would successfully resolve to the `no-typos` rule of that plugin. +* (Not shown in the config tree above): If `eslint-plugin-react` has a shareable config called `recommended`: + * If the user includes `extends: "plugin:foo::react/recommended"`, then they will extend the `recommended` config from the version of `eslint-plugin-react` used in `eslint-config-foo`. (This is similar to the existing pattern of `extends: "plugin:react/recommended"`, except that the name of the plugin is now `foo::react`, similar to how it's used in other places.) + * If the `recommended` shareable config in `eslint-plugin-react` itself depends on another plugin called `baz` with a rule `qux`, then a user could configure this rule using `baz/qux`, `foo::baz/qux`, or `foo::plugin:react/recommended::baz/qux`. (As before, the latter would only be necessary there are two configs named `baz` both accessible from `eslint-config-foo`.) ### Notable properties of this design From 996fe9af4517cd82d26cb2eba2d56d1984b3d115 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Thu, 6 Dec 2018 13:56:21 -0500 Subject: [PATCH 12/14] Remove note about dropping support for home-directory configs --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index 8b0f058e..ee5f05c7 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -214,7 +214,7 @@ As a sidenote, this example demonstrates the presence of the `plugins` field in ### Home-directory configs which depend on shareable configs will usually stop working -With this change, config files in a home directory will attempt to load their shareable configs from the home directory, usually resulting in an error. (Previously, these home-directory configs would implicitly depend on having certain plugins available from whatever version of ESLint was being used to load them.) Given this constraint, we might consider simply dropping support for home-directory configs, as has been discussed separately from this proposal in the past. +With this change, config files in a home directory will attempt to load their shareable configs from the home directory, usually resulting in an error. (Previously, these home-directory configs would implicitly depend on having certain plugins available from whatever version of ESLint was being used to load them.) ### Users are exposed to some details about the structure of their shareable configs From dbf696d70c12ec19fb55565d6fbc03f098644fae Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Thu, 6 Dec 2018 14:42:54 -0500 Subject: [PATCH 13/14] Weaken the claim about required user understanding --- designs/2018-relative-package-loading/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index ee5f05c7..d98ad68a 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -133,7 +133,7 @@ The implementation is not expected to affect config caching logic; configs can b This proposal has a few backwards-incompatible aspects, so it would appear in a migration guide for the major version where it's introduced. It would also entail updating plugin documentation to suggest adding shareable configs as dependencies, and removing documentation about the difference between local and global ESLint installations. -Importantly, **it is *not* necessary for users to understand the details of hierarchial name resolution**. This should decrease cognitive load for users, and reduce the amount of documentation they need to read in order to create a config. +Importantly, **it is *not* necessary for users to understand the details of how hierarchial names are resolved** in order to create a config. This should decrease cognitive load for users, and reduce the amount of documentation they need to read in order to create a config. To see why this is the case, consider three common usage scenarios: From 4f0c43a5d6c407b379e69425fcff0a1686c2c74b Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Thu, 6 Dec 2018 15:33:24 -0500 Subject: [PATCH 14/14] Add an alternative to always load configs/plugins from the project root --- designs/2018-relative-package-loading/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/designs/2018-relative-package-loading/README.md b/designs/2018-relative-package-loading/README.md index d98ad68a..b18da60a 100644 --- a/designs/2018-relative-package-loading/README.md +++ b/designs/2018-relative-package-loading/README.md @@ -243,6 +243,16 @@ One alternative solution would be to avoid the complexity of hierarchical rule n However, with that solution the user has little recourse if two shareable configs both depend on the same plugin, resulting in a "dependency hell" scenario where many pairs of shareable configs would be incompatible with each other due to different dependency versions. +### Resolve all plugins and shareable configs from the user's project root + +Instead of loading plugins and shareable configs relative to the ESLint package location (the current behavior), ESLint could resolve plugins and shareable configs from the end user's project root. Shareable configs would continue to declare plugins as `peerDependencies`, and users would still be required to manually install them. + +This solution would have the advantage being logically sound with regard to package-loading (the user would be adding plugins/configs as `devDependencies`, and the packages would correctly be loaded relative to the user's project rather than from somewhere else). As a result, this solution would fix [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125). This solution would also eliminate the behavior switch between local and global installations, since the location of the ESLint package would no longer be relevant to how plugins get loaded. + +This solution does not address the goal of allowing shareable config authors to manage their plugins; end users would still be required to install the plugins that their shareable configs need. As a result, with this solution there would still be a significant burden on shareable configs aiming to add new plugins. Plugin names would still need to be unique, which could inhibit the design of other plugin-loading enhancements (e.g. loading plugins by path). However, this solution would provide a way to fix [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125) without introducing the complexity of handling plugin name conflicts, and it would likely be compatible with future solutions to the problem of delegating plugin management to a third party. + +(Something similar to this solution was implemented in [`b46c893`](https://github.com/eslint/eslint/commit/b46c893e67cceafccd20ab2eef9048d22c82b067). However, that implementation only applied to shareable configs rather than plugins, which is insufficient for package-loading soundness. Additionally, that implementation had a bug that made it ineffective in almost all cases.) + ### Use plugins that pull rules from other plugins, instead of shareable configs [eslint/eslint#3458 (comment)](https://github.com/eslint/eslint/issues/3458#issuecomment-257161846) proposed solving the duplicate-name problem by using plugins that depend on other plugins and reexport the rules of their dependencies, without any changes to ESLint core. It suggested two possible ways of re-exporting the rules: either a plugin could export them directly with the same name, or it could give the names a common prefix. This was proposed to address the issue that end users are exposed to their configs' dependencies.