Skip to content

Commit 5a8b494

Browse files
committed
Merge pull request #227 from benmosher/resolvers-v2
prototype of clearer resolver interface
2 parents 5c9c3b9 + 5d21468 commit 5a8b494

20 files changed

+213
-110
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ node_js:
66
- stable
77

88
install:
9-
- npm -g install npm@2
9+
- npm -g install npm@3
1010
- npm install
1111
# install all resolver deps
1212
- "for resolver in ./resolvers/*; do cd $resolver && npm install && cd ../..; done"

CHANGELOG.md

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
55

6-
## [Unreleased]
6+
## [1.4.0] - 2016-03-25
7+
### Added
8+
- Resolver plugin interface v2: more explicit response format that more clearly covers the found-but-core-module case, where there is no path.
9+
Still backwards-compatible with the original version of the resolver spec.
10+
- [Resolver documentation](./resolvers/README.md)
11+
712
### Changed
813
- using `package.json/files` instead of `.npmignore` for package file inclusion ([#228], thanks [@mathieudutour])
14+
- using `es6-*` ponyfills instead of `babel-runtime`
915

1016
## [1.3.0] - 2016-03-20
1117
Major perf improvements. Between parsing only once and ignoring gigantic, non-module `node_modules`,
@@ -137,7 +143,8 @@ for info on changes for earlier releases.
137143
[#119]: https://github.com/benmosher/eslint-plugin-import/issues/119
138144
[#89]: https://github.com/benmosher/eslint-plugin-import/issues/89
139145

140-
[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v1.3.0...HEAD
146+
[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v1.4.0...HEAD
147+
[1.4.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.3.0...v1.4.0
141148
[1.3.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.2.0...v1.3.0
142149
[1.2.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.1.0...v1.2.0
143150
[1.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.0.4...v1.1.0

README.md

+6-42
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ rules:
8585
# etc...
8686
```
8787

88-
# Resolver plugins
88+
# Resolvers
8989

9090
With the advent of module bundlers and the current state of modules and module
9191
syntax specs, it's not always obvious where `import x from 'module'` should look
@@ -99,48 +99,10 @@ Node does not, such as loaders (`import 'file!./whatever'`) and a number of
9999
aliasing schemes, such as [`externals`]: mapping a module id to a global name at
100100
runtime (allowing some modules to be included more traditionally via script tags).
101101

102-
In the interest of supporting both of these, v0.11 introduces resolver plugins.
103-
At the moment, these are modules exporting a single function:
104-
105-
```js
106-
107-
exports.resolveImport = function (source, file, config) {
108-
// return source's absolute path given
109-
// - file: absolute path of importing module
110-
// - config: optional config provided for this resolver
111-
112-
// return `null` if source is a "core" module (i.e. "fs", "crypto") that
113-
// can't be found on the filesystem
114-
}
115-
```
116-
117-
The default `node` plugin that uses [`resolve`] is a handful of lines:
118-
119-
```js
120-
var resolve = require('resolve')
121-
, path = require('path')
122-
, assign = require('object-assign')
123-
124-
exports.resolveImport = function resolveImport(source, file, config) {
125-
if (resolve.isCore(source)) return null
126-
127-
return resolve.sync(source, opts(path.dirname(file), config))
128-
}
129-
130-
function opts(basedir, config) {
131-
return assign( {}
132-
, config
133-
, { basedir: basedir }
134-
)
135-
}
136-
```
137-
138-
It essentially just uses the current file to get a reference base directory (`basedir`)
139-
and then passes through any explicit config from the `.eslintrc`; things like
140-
non-standard file extensions, module directories, etc.
102+
In the interest of supporting both of these, v0.11 introduces resolvers.
141103

142104
Currently [Node] and [Webpack] resolution have been implemented, but the
143-
resolvers are just npm packages, so third party packages are supported (and encouraged!).
105+
resolvers are just npm packages, so [third party packages are supported](https://github.com/benmosher/eslint-plugin-import/wiki/Resolvers) (and encouraged!).
144106

145107
Just install a resolver as `eslint-import-resolver-foo` and reference it as such:
146108

@@ -157,6 +119,8 @@ settings:
157119
foo: { someConfigKey: value }
158120
```
159121
122+
If you are interesting in writing a resolver, see the [spec](./resolvers/README.md) for more details.
123+
160124
[`resolve`]: https://www.npmjs.com/package/resolve
161125
[`externals`]: http://webpack.github.io/docs/library-and-externals.html
162126

@@ -193,7 +157,7 @@ settings:
193157

194158
#### `import/resolver`
195159

196-
See [resolver plugins](#resolver-plugins).
160+
See [resolvers](#resolvers).
197161

198162
#### `import/cache`
199163

appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ install:
1919
- ps: Install-Product node $env:nodejs_version
2020

2121
# update npm (only needed for Node 0.10)
22-
# - npm -g install npm@2
22+
- npm -g install npm@3
2323
# - set PATH=%APPDATA%\npm;%PATH%
2424

2525
# install modules

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-import",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "Import with sanity.",
55
"main": "lib/index.js",
66
"directories": {
@@ -16,6 +16,7 @@
1616
"cover": "gulp pretest && cross-env NODE_PATH=./lib istanbul cover --dir reports/coverage _mocha tests/lib/ -- --recursive -R progress",
1717
"posttest": "eslint ./src",
1818
"test": "cross-env NODE_PATH=./lib gulp test",
19+
"test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done",
1920
"ci-test": "eslint ./src && gulp pretest && cross-env NODE_PATH=./lib istanbul cover --report lcovonly --dir reports/coverage _mocha tests/lib/ -- --recursive --reporter dot",
2021
"debug": "cross-env NODE_PATH=./lib mocha debug --recursive --reporter dot tests/lib/",
2122
"prepublish": "gulp prepublish",
@@ -68,7 +69,7 @@
6869
"es6-map": "^0.1.3",
6970
"es6-set": "^0.1.4",
7071
"es6-symbol": "*",
71-
"eslint-import-resolver-node": "^0.1.0",
72+
"eslint-import-resolver-node": "^0.2.0",
7273
"object-assign": "^4.0.1"
7374
}
7475
}

resolvers/README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Resolver spec (v2)
2+
3+
Resolvers must export two names:
4+
5+
### `interfaceVersion => Number`
6+
7+
This document currently describes version 2 of the resolver interface. As such, a resolver implementing this version should
8+
9+
```js
10+
export const interfaceVersion = 2
11+
```
12+
or
13+
```js
14+
exports.interfaceVersion = 2
15+
```
16+
17+
To the extent it is feasible, trailing versions of the resolvers will continue to be supported, at least until a major version bump on the plugin proper.
18+
19+
Currently, version 1 is assumed if no `interfaceVersion` is available. (didn't think to define it until v2, heh. 😅)
20+
21+
### `resolve(source, file, config) => { found: Boolean, path: String? }`
22+
23+
Given:
24+
```js
25+
// /some/path/to/module.js
26+
import ... from './imported-file'
27+
```
28+
29+
and
30+
31+
```yaml
32+
# .eslintrc.yml
33+
---
34+
settings:
35+
import/resolver:
36+
my-cool-resolver: [some, stuff]
37+
node: { paths: [a, b, c] }
38+
```
39+
40+
#### Arguments
41+
42+
The arguments provided will be:
43+
44+
##### `source`
45+
the module identifier (`./imported-file`).
46+
47+
##### `file`
48+
the absolute path to the file making the import (`/some/path/to/module.js`)
49+
50+
##### `config`
51+
52+
an object provided via the `import/resolver` setting.`my-cool-resolver` will get `["some", "stuff"]` as its `config`, while
53+
`node` will get `{ "paths": ["a", "b", "c"] }` provided as `config`.
54+
55+
#### Return value
56+
57+
The first resolver to return `{found: true}` is considered the source of truth. The returned object has:
58+
59+
- `found`: `true` if the `source` module can be resolved relative to `file`, else `false`
60+
- `path`: an absolute path `String` if the module can be located on the filesystem; else, `null`.
61+
62+
An example of a `null` path is a Node core module, such as `fs` or `crypto`. These modules can always be resolved, but the path need not be provided as the plugin will not attempt to parse core modules at this time.
63+
64+
If the resolver cannot resolve `source` relative to `file`, it should just return `{ found: false }`. No `path` key is needed in this case.
65+
66+
## Example
67+
68+
Here is most of the [Node resolver] at the time of this writing. It is just a wrapper around substack/Browserify's synchronous [`resolve`]:
69+
70+
```js
71+
var resolve = require('resolve')
72+
73+
exports.resolve = function (source, file, config) {
74+
if (resolve.isCore(source)) return { found: true, path: null }
75+
try {
76+
return { found: true, path: resolve.sync(source, opts(file, config)) }
77+
} catch (err) {
78+
return { found: false }
79+
}
80+
}
81+
```
82+
83+
[Node resolver]: ./node/index.js
84+
[`resolve`]: https://www.npmjs.com/package/resolve

resolvers/node/.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test/

resolvers/node/index.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ var resolve = require('resolve')
22
, path = require('path')
33
, assign = require('object-assign')
44

5-
exports.resolveImport = function resolveImport(source, file, config) {
6-
if (resolve.isCore(source)) return null
5+
exports.interfaceVersion = 2
76

8-
return resolve.sync(source, opts(file, config))
7+
exports.resolve = function (source, file, config) {
8+
if (resolve.isCore(source)) return { found: true, path: null }
9+
try {
10+
return { found: true, path: resolve.sync(source, opts(file, config)) }
11+
} catch (err) {
12+
return { found: false }
13+
}
914
}
1015

1116
function opts(file, config) {

resolvers/node/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-import-resolver-node",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "Node default behavior import resolution plugin for eslint-plugin-import.",
55
"main": "index.js",
66
"scripts": {
@@ -27,6 +27,9 @@
2727
"object-assign": "^4.0.1",
2828
"resolve": "^1.1.6"
2929
},
30+
"peerDependencies": {
31+
"eslint-plugin-import": ">=1.4.0"
32+
},
3033
"devDependencies": {
3134
"chai": "^3.4.1",
3235
"mocha": "^2.3.4"

resolvers/node/test/paths.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ var node = require('../index.js')
55

66
describe("paths", function () {
77
it("handles base path relative to CWD", function () {
8-
expect(node.resolveImport('../', './test/file.js'))
9-
.to.equal(path.resolve(__dirname, '../index.js'))
8+
expect(node.resolve('../', './test/file.js'))
9+
.to.have.property('path')
10+
.equal(path.resolve(__dirname, '../index.js'))
1011
})
1112
})

resolvers/webpack/index.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ var findRoot = require('find-root')
88

99
var resolveAlias = require('./resolve-alias')
1010

11+
exports.interfaceVersion = 2
12+
1113
/**
1214
* Find the full path to 'source', given 'file' as a full reference path.
1315
*
@@ -18,15 +20,15 @@ var resolveAlias = require('./resolve-alias')
1820
* @return {string?} the resolved path to source, undefined if not resolved, or null
1921
* if resolved to a non-FS resource (i.e. script tag at page load)
2022
*/
21-
exports.resolveImport = function resolveImport(source, file, settings) {
23+
exports.resolve = function (source, file, settings) {
2224

2325
// strip loaders
2426
var finalBang = source.lastIndexOf('!')
2527
if (finalBang >= 0) {
2628
source = source.slice(finalBang + 1)
2729
}
2830

29-
if (resolve.isCore(source)) return null
31+
if (resolve.isCore(source)) return { found: true, path: null }
3032

3133
var configPath = get(settings, 'config', 'webpack.config.js')
3234
, webpackConfig
@@ -46,7 +48,7 @@ exports.resolveImport = function resolveImport(source, file, settings) {
4648
}
4749

4850
// externals
49-
if (findExternal(source, webpackConfig.externals)) return null
51+
if (findExternal(source, webpackConfig.externals)) return { found: true, path: null }
5052

5153
// replace alias if needed
5254
source = resolveAlias(source, get(webpackConfig, ['resolve', 'alias'], {}))
@@ -61,20 +63,24 @@ exports.resolveImport = function resolveImport(source, file, settings) {
6163
}
6264

6365
// otherwise, resolve "normally"
64-
return resolve.sync(source, {
65-
basedir: path.dirname(file),
66+
try {
67+
return { found: true, path: resolve.sync(source, {
68+
basedir: path.dirname(file),
6669

67-
// defined via http://webpack.github.io/docs/configuration.html#resolve-extensions
68-
extensions: get(webpackConfig, ['resolve', 'extensions'])
69-
|| ['', '.webpack.js', '.web.js', '.js'],
70+
// defined via http://webpack.github.io/docs/configuration.html#resolve-extensions
71+
extensions: get(webpackConfig, ['resolve', 'extensions'])
72+
|| ['', '.webpack.js', '.web.js', '.js'],
7073

71-
// http://webpack.github.io/docs/configuration.html#resolve-modulesdirectories
72-
moduleDirectory: get(webpackConfig, ['resolve', 'modulesDirectories'])
73-
|| ['web_modules', 'node_modules'],
74+
// http://webpack.github.io/docs/configuration.html#resolve-modulesdirectories
75+
moduleDirectory: get(webpackConfig, ['resolve', 'modulesDirectories'])
76+
|| ['web_modules', 'node_modules'],
7477

75-
paths: paths,
76-
packageFilter: packageFilter.bind(null, webpackConfig),
77-
})
78+
paths: paths,
79+
packageFilter: packageFilter.bind(null, webpackConfig),
80+
}) }
81+
} catch (err) {
82+
return { found: false }
83+
}
7884
}
7985

8086
function findExternal(source, externals) {

resolvers/webpack/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-import-resolver-webpack",
3-
"version": "0.1.5",
3+
"version": "0.2.0",
44
"description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.",
55
"main": "index.js",
66
"scripts": {
@@ -30,6 +30,9 @@
3030
"lodash.get": "^3.7.0",
3131
"resolve": "^1.1.6"
3232
},
33+
"peerDependencies": {
34+
"eslint-plugin-import": ">=1.4.0"
35+
},
3336
"devDependencies": {
3437
"chai": "^3.4.1",
3538
"mocha": "^2.3.3"

resolvers/webpack/test/alias.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ var webpack = require('../index')
77
var file = path.join(__dirname, 'files', 'dummy.js')
88

99
describe("resolve.alias", function () {
10-
it("works", function () {
11-
expect(webpack.resolveImport('foo', file)).to.exist
10+
var resolved
11+
before(function () { resolved = webpack.resolve('foo', file) })
12+
13+
it("is found", function () { expect(resolved).to.have.property('found', true) })
14+
15+
it("is correct", function () {
16+
expect(resolved).to.have.property('path')
1217
.and.equal(path.join(__dirname, 'files', 'some', 'goofy', 'path', 'foo.js'))
1318
})
1419
})

0 commit comments

Comments
 (0)