Skip to content

Commit 8a68fbb

Browse files
committed
support linting JS with alt-JS dependencies via import/parsers setting! (closes #407)
1 parent 852d14b commit 8a68fbb

File tree

9 files changed

+126
-16
lines changed

9 files changed

+126
-16
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
55

66
## [Unreleased]
7+
### Added
8+
- [`import/parsers` setting]: parse some dependencies (i.e. TypeScript!) with a different parser than the ESLint-configured parser.
9+
710
### Fixed
811
- [`namespace`] exception for get property from `namespace` import, which are re-export from commonjs module ([#416])
912

@@ -260,6 +263,7 @@ for info on changes for earlier releases.
260263
[`import/cache` setting]: ./README.md#importcache
261264
[`import/ignore` setting]: ./README.md#importignore
262265
[`import/extensions` setting]: ./README.md#importextensions
266+
[`import/parsers` setting]: ./README.md#importparsers
263267
[`import/core-modules` setting]: ./README.md#importcore-modules
264268
[`import/external-module-folders` setting]: ./README.md#importexternal-module-folders
265269

Diff for: README.md

+26
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,32 @@ Contribution of more such shared configs for other platforms are welcome!
265265

266266
An array of folders. Resolved modules only from those folders will be considered as "external". By default - `["node_modules"]`. Makes sense if you have configured your path or webpack to handle your internal paths differently and want to considered modules from some folders, for example `bower_components` or `jspm_modules`, as "external".
267267

268+
#### `import/parsers`
269+
270+
A map from parsers to file extension arrays. If a file extension is matched, the
271+
dependency parser will require and use the map key as the parser instead of the
272+
configured ESLint parser. This is useful if you're inter-op-ing with TypeScript
273+
directly using Webpack, for example:
274+
275+
```yaml
276+
# .eslintrc.yml
277+
settings:
278+
import/parsers:
279+
typescript-eslint-parser: [ .ts, .tsx ]
280+
```
281+
282+
In this case, [`typescript-eslint-parser`](https://github.com/eslint/typescript-eslint-parser) must be installed and require-able from
283+
the running `eslint` module's location (i.e., install it as a peer of ESLint).
284+
285+
This is currently only tested with `typescript-eslint-parser` but should theoretically
286+
work with any moderately ESTree-compliant parser.
287+
288+
It's difficult to say how well various plugin features will be supported, too,
289+
depending on how far down the rabbit hole goes. Submit an issue if you find strange
290+
behavior beyond here, but steel your heart against the likely outcome of closing
291+
with `wontfix`.
292+
293+
268294
#### `import/resolver`
269295

270296
See [resolvers](#resolvers).

Diff for: package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@
5353
"coveralls": "^2.11.4",
5454
"cross-env": "^2.0.0",
5555
"eslint": "2.x",
56-
"eslint-plugin-import": "next",
5756
"eslint-import-resolver-node": "file:./resolvers/node",
5857
"eslint-import-resolver-webpack": "file:./resolvers/webpack",
58+
"eslint-plugin-import": "next",
5959
"gulp": "^3.9.0",
6060
"gulp-babel": "6.1.2",
6161
"istanbul": "^0.4.0",
6262
"mocha": "^2.2.1",
6363
"nyc": "^7.0.0",
6464
"redux": "^3.0.4",
65-
"rimraf": "2.5.2"
65+
"rimraf": "2.5.2",
66+
"typescript": "^1.8.10",
67+
"typescript-eslint-parser": "^0.1.1"
6668
},
6769
"peerDependencies": {
6870
"eslint": "2.x - 3.x"

Diff for: src/core/getExports.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import * as fs from 'fs'
55
import { createHash } from 'crypto'
66
import * as doctrine from 'doctrine'
77

8+
import debug from 'debug'
9+
810
import parse from './parse'
911
import resolve, { relative as resolveRelative } from './resolve'
1012
import isIgnored, { hasValidExtension } from './ignore'
1113

1214
import { hashObject } from './hash'
1315

16+
const log = debug('eslint-plugin-import:ExportMap')
17+
1418
const exportCache = new Map()
1519

1620
/**
@@ -96,8 +100,9 @@ export default class ExportMap {
96100
var m = new ExportMap(path)
97101

98102
try {
99-
var ast = parse(content, context)
103+
var ast = parse(path, content, context)
100104
} catch (err) {
105+
log('parse error:', path, err)
101106
m.errors.push(err)
102107
return m // can't continue
103108
}

Diff for: src/core/ignore.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,27 @@ function validExtensions({ settings }) {
1313
// breaking: default to '.js'
1414
// cachedSet = new Set(settings['import/extensions'] || [ '.js' ])
1515
cachedSet = 'import/extensions' in settings
16-
? new Set(settings['import/extensions'])
16+
? makeValidExtensionSet(settings)
1717
: { has: () => true } // the set of all elements
1818

1919
return cachedSet
2020
}
2121

22+
function makeValidExtensionSet(settings) {
23+
// start with explicit JS-parsed extensions
24+
const exts = new Set(settings['import/extensions'])
25+
26+
// all alternate parser extensions are also valid
27+
if ('import/parsers' in settings) {
28+
for (let parser in settings['import/parsers']) {
29+
settings['import/parsers'][parser]
30+
.forEach(ext => exts.add(ext))
31+
}
32+
}
33+
34+
return exts
35+
}
36+
2237
export default function ignore(path, context) {
2338
// ignore node_modules by default
2439
const ignoreStrings = context.settings['import/ignore']

Diff for: src/core/parse.js

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import moduleRequire from './module-require'
22
import assign from 'object-assign'
3+
import { extname } from 'path'
4+
import debug from 'debug'
35

4-
export default function (content, context) {
6+
const log = debug('eslint-plugin-import:parse')
7+
8+
export default function (path, content, context) {
59

610
if (context == null) throw new Error('need context to parse properly')
711

8-
let { parserOptions, parserPath } = context
12+
let { parserOptions } = context
13+
const parserPath = getParserPath(path, context)
914

1015
if (!parserPath) throw new Error('parserPath is required!')
1116

@@ -21,3 +26,19 @@ export default function (content, context) {
2126

2227
return parser.parse(content, parserOptions)
2328
}
29+
30+
function getParserPath(path, context) {
31+
const parsers = context.settings['import/parsers']
32+
if (parsers != null) {
33+
const extension = extname(path)
34+
for (let parserPath in parsers) {
35+
if (parsers[parserPath].indexOf(extension) > -1) {
36+
// use this alternate parser
37+
log('using alt parser:', parserPath)
38+
return parserPath
39+
}
40+
}
41+
}
42+
// default to use ESLint parser
43+
return context.parserPath
44+
}

Diff for: tests/files/typescript.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
type X = { y: string | null }
1+
type X = string
22

3-
export function getX() : X {
4-
return null
3+
export function getFoo() : X {
4+
return "foo"
55
}

Diff for: tests/src/core/getExports.js

+37-3
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,12 @@ describe('getExports', function () {
8383
var imports = ExportMap.parse(
8484
path,
8585
contents,
86-
{ parserPath: 'babel-eslint' }
86+
{ parserPath: 'babel-eslint', settings: {} }
8787
)
8888

89-
expect(imports).to.exist
90-
expect(imports.get('default')).to.exist
89+
expect(imports, 'imports').to.exist
90+
expect(imports.errors).to.be.empty
91+
expect(imports.get('default'), 'default export').to.exist
9192
expect(imports.has('Bar')).to.be.true
9293
})
9394

@@ -187,6 +188,7 @@ describe('getExports', function () {
187188
sourceType: 'module',
188189
attachComment: true,
189190
},
191+
settings: {},
190192
})
191193
})
192194

@@ -197,6 +199,7 @@ describe('getExports', function () {
197199
sourceType: 'module',
198200
attachComment: true,
199201
},
202+
settings: {},
200203
})
201204
})
202205
})
@@ -307,4 +310,35 @@ describe('getExports', function () {
307310

308311
})
309312

313+
context('alternate parsers', function () {
314+
const configs = [
315+
// ['string form', { 'typescript-eslint-parser': '.ts' }],
316+
['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }],
317+
]
318+
319+
configs.forEach(([description, parserConfig]) => {
320+
describe(description, function () {
321+
const context = Object.assign({}, fakeContext,
322+
{ settings: {
323+
'import/extensions': ['.js'],
324+
'import/parsers': parserConfig,
325+
} })
326+
327+
let imports
328+
before('load imports', function () {
329+
imports = ExportMap.get('./typescript.ts', context)
330+
})
331+
332+
it('returns something for a TypeScript file', function () {
333+
expect(imports).to.exist
334+
})
335+
336+
it('has export (getFoo)', function () {
337+
expect(imports.has('getFoo')).to.be.true
338+
})
339+
})
340+
})
341+
342+
})
343+
310344
})

Diff for: tests/src/core/parse.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import parse from 'core/parse'
55
import { getFilename } from '../utils'
66

77
describe('parse(content, { settings, ecmaFeatures })', function () {
8+
const path = getFilename('jsx.js')
89
let content
910

1011
before((done) =>
11-
fs.readFile(getFilename('jsx.js'), { encoding: 'utf8' },
12+
fs.readFile(path, { encoding: 'utf8' },
1213
(err, f) => { if (err) { done(err) } else { content = f; done() }}))
1314

14-
it("doesn't support JSX by default", function () {
15-
expect(() => parse(content, { parserPath: 'espree' })).to.throw(Error)
15+
it('doesn\'t support JSX by default', function () {
16+
expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error)
1617
})
18+
1719
it('infers jsx from ecmaFeatures when using stock parser', function () {
18-
expect(() => parse(content, { parserPath: 'espree', parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true } } }))
20+
expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true } } }))
1921
.not.to.throw(Error)
2022
})
23+
2124
})

0 commit comments

Comments
 (0)