Skip to content
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

Breaking: simplify config/plugin/parser resolution (fixes #10125) #11388

Merged
merged 20 commits into from Apr 4, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8df9633
Breaking: simplify config/plugin/parser resolution (fixes #10125)
not-an-aardvark Feb 13, 2019
7eb8cbc
Avoid adding baseDirectory and filePath property to configs internally
not-an-aardvark Feb 13, 2019
901d60f
Add special behavior for "espree" parser
not-an-aardvark Feb 13, 2019
15ea34e
Remove temporary line from test fixture
not-an-aardvark Feb 13, 2019
503294e
Improve error message for missing parser
not-an-aardvark Feb 13, 2019
202ddad
Fix typos
not-an-aardvark Feb 14, 2019
859020d
Replace placeholder.js with __placeholder.js__
not-an-aardvark Feb 14, 2019
49ba279
Avoid null case in config-initializer JSDoc comment
not-an-aardvark Feb 20, 2019
20cba2b
Reduce the number of parameters for `ConfigFile.loadObject`
not-an-aardvark Feb 20, 2019
fc166ca
Add plugin root path to missing-plugin message
not-an-aardvark Feb 20, 2019
e0840d6
Ensure plugin-missing error message is used when extending plugin con…
not-an-aardvark Feb 20, 2019
53b738b
Ensure latest config is present in config stack for plugin-missing er…
not-an-aardvark Feb 20, 2019
6a9729d
Fix new linting errors
not-an-aardvark Feb 22, 2019
f8459c6
Remove note about --config accepting a shareable config
not-an-aardvark Mar 13, 2019
efad812
Merge branch 'master' into config-loading-simplification
not-an-aardvark Mar 19, 2019
45e1149
Revert most changes to config-initializer
not-an-aardvark Mar 19, 2019
12ac99c
Avoid useless concatenation
not-an-aardvark Apr 1, 2019
a5b875f
Make plugin-missing message less verbose
not-an-aardvark Apr 1, 2019
caf1818
Remove outdated JSDoc
not-an-aardvark Apr 2, 2019
a1ec8e1
Remove outdated JSDoc, for real this time
not-an-aardvark Apr 2, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 3 additions & 38 deletions README.md
Expand Up @@ -28,11 +28,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J

Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.

There are two ways to install ESLint: globally and locally.

### Local Installation and Usage

If you want to include ESLint as part of your project's build system, we recommend installing it locally. You can do so using npm:
You can install ESLint using npm:

```
$ npm install eslint --save-dev
Expand All @@ -50,31 +46,7 @@ After that, you can run ESLint on any file or directory like this:
$ ./node_modules/.bin/eslint yourfile.js
```

Any plugins or shareable configs that you use must also be installed locally to work with a locally-installed ESLint.

### Global Installation and Usage

If you want to make ESLint available to tools that run across all of your projects, we recommend installing ESLint globally. You can do so using npm:

```
$ npm install -g eslint
```

You should then set up a configuration file:

```
$ eslint --init
```

After that, you can run ESLint on any file or directory like this:

```
$ eslint yourfile.js
```

Any plugins or shareable configs that you use must also be installed globally to work with a globally-installed ESLint.

**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.

## Configuration

Expand Down Expand Up @@ -124,16 +96,9 @@ No, ESLint does both traditional linting (looking for problematic patterns) and

### Why can't ESLint find my plugins?

ESLint can be [globally or locally installed](#installation-and-usage). If you install ESLint globally, your plugins must also be installed globally; if you install ESLint locally, your plugins must also be installed locally.

If you are trying to run globally, make sure your plugins are installed globally (use `npm ls -g`).

If you are trying to run locally:

* Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime).
* Make sure you have run `npm install` and all your dependencies are installed.

In all cases, make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has.
* Make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has.

### Does ESLint support JSX?

Expand Down
6 changes: 2 additions & 4 deletions docs/developer-guide/nodejs-api.md
Expand Up @@ -273,10 +273,8 @@ Map {

### Linter#defineParser

Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically you can add this function
with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers) as second argument.

If during linting the parser is not found, it will fallback to `require(parserId)`.
Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically, you can add this function
with the name of the parser as first argument and the [parser object](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance.

```js
const Linter = require("eslint").Linter;
Expand Down
2 changes: 2 additions & 0 deletions docs/developer-guide/shareable-configs.md
Expand Up @@ -38,6 +38,8 @@ You should declare your dependency on ESLint in `package.json` using the [peerDe
}
```

If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a third-party parser or another shareable config, you can specify these packages as `dependencies`.

You can also test your shareable config on your computer before publishing by linking your module globally. Type:

```bash
Expand Down
8 changes: 0 additions & 8 deletions docs/user-guide/command-line-interface.md
Expand Up @@ -111,14 +111,6 @@ Example:

This example uses the configuration file at `~/my-eslint.json`.

It also accepts a module ID of a [sharable config](../developer-guide/shareable-configs).

Example:

eslint -c myconfig file.js

This example directly uses the sharable config `eslint-config-myconfig`.

If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations will be merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files.

#### `--env`
Expand Down
17 changes: 7 additions & 10 deletions docs/user-guide/configuring.md
Expand Up @@ -60,9 +60,8 @@ Setting parser options helps ESLint determine what is a parsing error. All langu

By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements:

1. It must be an npm module installed locally.
1. It must have an Esprima-compatible interface (it must export a `parse()` method).
1. It must produce Esprima-compatible AST and token objects.
1. It must be a Node module loadable from the config file where it appears. Usually, this means you should install the parser package separately using npm.
1. It must conform to the [parser interface](/docs/developer-guide/working-with-plugins.md#working-with-custom-parsers).

Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers.

Expand Down Expand Up @@ -255,7 +254,7 @@ For historical reasons, the boolean value `false` and the string value `"readabl

## Configuring Plugins

ESLint supports the use of third-party plugins. Before using the plugin you have to install it using npm.
ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm.

To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name.

Expand All @@ -277,7 +276,7 @@ And in YAML:
- eslint-plugin-plugin2
```

**Note:** Due to the behavior of Node's `require` function, a globally-installed instance of ESLint can only use globally-installed ESLint plugins, and locally-installed version can only use *locally-installed* plugins. Mixing local and global plugins is not supported.
**Note:** Plugins are resolved relative to the current working directory of the ESLint process. In other words, ESLint will load the same plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in a Node REPL from their project root.

## Configuring Rules

Expand Down Expand Up @@ -625,10 +624,10 @@ A configuration file can extend the set of enabled rules from base configuration

The `extends` property value is either:

* a string that specifies a configuration
* a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`)
* an array of strings: each additional configuration extends the preceding configurations

ESLint extends configurations recursively so a base configuration can also have an `extends` property.
ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear.

The `rules` property can do any of the following to extend (or override) the set of rules:

Expand Down Expand Up @@ -723,9 +722,7 @@ Example of a configuration file in JSON format:

### Using a configuration file

The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files).

ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it **unless** that file is in your home directory or a directory that isn't an ancestor to the directory in which ESLint is installed (either locally or globally). In those cases, ESLint resolves the relative path to the base file relative to the linted **project** directory (typically the current working directory).
The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it.

Example of a configuration file in JSON format:

Expand Down
44 changes: 4 additions & 40 deletions docs/user-guide/getting-started.md
Expand Up @@ -15,61 +15,25 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J

Prerequisites: [Node.js](https://nodejs.org/en/) (>=6.14), npm version 3+.

There are two ways to install ESLint: globally and locally.

### Local Installation and Usage

If you want to include ESLint as part of your project's build system, we recommend installing it locally. You can do so using npm:
You can install ESLint using npm:

```
$ npm install eslint --save-dev
```

You should then setup a configuration file:
You should then set up a configuration file:

```
$ ./node_modules/.bin/eslint --init
```

After that, you can run ESLint in your project's root directory like this:

```
$ ./node_modules/.bin/eslint yourfile.js
```

Instead of navigating to `./node_modules/.bin/` you may also use `npx` to run `eslint`:

```
$ npx eslint
```

**Note:** If ESLint wasn't manually installed (via `npm`), `npx` will install `eslint` to a temporary directory and execute it.

Any plugins or shareable configs that you use must also be installed locally to work with a locally-installed ESLint.

### Global Installation and Usage

If you want to make ESLint available to tools that run across all of your projects, we recommend installing ESLint globally. You can do so using npm:

```
$ npm install -g eslint
```

You should then setup a configuration file:

```
$ eslint --init
```

After that, you can run ESLint on any file or directory like this:

```
$ eslint yourfile.js
$ ./node_modules/.bin/eslint yourfile.js
```

Any plugins or shareable configs that you use must also be installed globally to work with a globally-installed ESLint.

**Note:** `eslint --init` is intended for setting up and configuring ESLint on a per-project basis and will perform a local installation of ESLint and its plugins in the directory in which it is run. If you prefer using a global installation of ESLint, any plugins used in your configuration must also be installed globally.
It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.

## Configuration

Expand Down
34 changes: 28 additions & 6 deletions lib/cli-engine.js
Expand Up @@ -27,13 +27,12 @@ const fs = require("fs"),
globUtils = require("./util/glob-utils"),
validator = require("./config/config-validator"),
hash = require("./util/hash"),
ModuleResolver = require("./util/module-resolver"),
relativeModuleResolver = require("./util/relative-module-resolver"),
naming = require("./util/naming"),
pkg = require("../package.json"),
loadRules = require("./load-rules");

const debug = require("debug")("eslint:cli-engine");
const resolver = new ModuleResolver();
const validFixTypes = new Set(["problem", "suggestion", "layout"]);

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -184,6 +183,13 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, repor
configHelper.plugins.loadAll(config.plugins);
}

if (config.parser) {
if (!path.isAbsolute(config.parser)) {
throw new Error(`Expected parser to be an absolute path but found ${config.parser}. This is a bug.`);
}
linter.defineParser(config.parser, require(config.parser));
}

const loadedPlugins = configHelper.plugins.getAll();

for (const plugin in loadedPlugins) {
Expand Down Expand Up @@ -463,7 +469,23 @@ class CLIEngine {
});
}

this.config = new Config(this.options, this.linter);
this.config = new Config(
{
cwd: this.options.cwd,
baseConfig: this.options.baseConfig,
rules: this.options.rules,
ignore: this.options.ignore,
ignorePath: this.options.ignorePath,
parser: this.options.parser,
parserOptions: this.options.parserOptions,
useEslintrc: this.options.useEslintrc,
envs: this.options.envs,
globals: this.options.globals,
configFile: this.options.configFile,
plugins: this.options.plugins
},
aladdin-add marked this conversation as resolved.
Show resolved Hide resolved
this.linter
);

if (this.options.cache) {
const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
Expand Down Expand Up @@ -764,16 +786,16 @@ class CLIEngine {

let formatterPath;

// if there's a slash, then it's a file
// if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
if (!namespace && normalizedFormatName.indexOf("/") > -1) {
formatterPath = path.resolve(cwd, normalizedFormatName);
} else {
try {
const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");

formatterPath = resolver.resolve(npmFormat, `${cwd}/node_modules`);
formatterPath = relativeModuleResolver(npmFormat, path.join(cwd, "__placeholder__.js"));
} catch (e) {
formatterPath = `./formatters/${normalizedFormatName}`;
formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
}
}

Expand Down