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

support alt-JS dependencies #503

Merged
merged 1 commit into from
Aug 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).

## [Unreleased]
### Added
- [`import/parsers` setting]: parse some dependencies (i.e. TypeScript!) with a different parser than the ESLint-configured parser.

### Fixed
- [`namespace`] exception for get property from `namespace` import, which are re-export from commonjs module ([#416])

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

Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,32 @@ Contribution of more such shared configs for other platforms are welcome!

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".

#### `import/parsers`

A map from parsers to file extension arrays. If a file extension is matched, the
dependency parser will require and use the map key as the parser instead of the
configured ESLint parser. This is useful if you're inter-op-ing with TypeScript
directly using Webpack, for example:

```yaml
# .eslintrc.yml
settings:
import/parsers:
typescript-eslint-parser: [ .ts, .tsx ]
```

In this case, [`typescript-eslint-parser`](https://github.com/eslint/typescript-eslint-parser) must be installed and require-able from
the running `eslint` module's location (i.e., install it as a peer of ESLint).

This is currently only tested with `typescript-eslint-parser` but should theoretically
work with any moderately ESTree-compliant parser.

It's difficult to say how well various plugin features will be supported, too,
depending on how far down the rabbit hole goes. Submit an issue if you find strange
behavior beyond here, but steel your heart against the likely outcome of closing
with `wontfix`.


#### `import/resolver`

See [resolvers](#resolvers).
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@
"coveralls": "^2.11.4",
"cross-env": "^2.0.0",
"eslint": "2.x",
"eslint-plugin-import": "next",
"eslint-import-resolver-node": "file:./resolvers/node",
"eslint-import-resolver-webpack": "file:./resolvers/webpack",
"eslint-plugin-import": "next",
"gulp": "^3.9.0",
"gulp-babel": "6.1.2",
"istanbul": "^0.4.0",
"mocha": "^2.2.1",
"nyc": "^7.0.0",
"redux": "^3.0.4",
"rimraf": "2.5.2"
"rimraf": "2.5.2",
"typescript": "^1.8.10",
"typescript-eslint-parser": "^0.1.1"
},
"peerDependencies": {
"eslint": "2.x - 3.x"
Expand Down
7 changes: 6 additions & 1 deletion src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import * as fs from 'fs'
import { createHash } from 'crypto'
import * as doctrine from 'doctrine'

import debug from 'debug'

import parse from './parse'
import resolve, { relative as resolveRelative } from './resolve'
import isIgnored, { hasValidExtension } from './ignore'

import { hashObject } from './hash'

const log = debug('eslint-plugin-import:ExportMap')

const exportCache = new Map()

/**
Expand Down Expand Up @@ -96,8 +100,9 @@ export default class ExportMap {
var m = new ExportMap(path)

try {
var ast = parse(content, context)
var ast = parse(path, content, context)
} catch (err) {
log('parse error:', path, err)
m.errors.push(err)
return m // can't continue
}
Expand Down
17 changes: 16 additions & 1 deletion src/core/ignore.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,27 @@ function validExtensions({ settings }) {
// breaking: default to '.js'
// cachedSet = new Set(settings['import/extensions'] || [ '.js' ])
cachedSet = 'import/extensions' in settings
? new Set(settings['import/extensions'])
? makeValidExtensionSet(settings)
: { has: () => true } // the set of all elements

return cachedSet
}

function makeValidExtensionSet(settings) {
// start with explicit JS-parsed extensions
const exts = new Set(settings['import/extensions'])

// all alternate parser extensions are also valid
if ('import/parsers' in settings) {
for (let parser in settings['import/parsers']) {
settings['import/parsers'][parser]
.forEach(ext => exts.add(ext))
}
}

return exts
}

export default function ignore(path, context) {
// ignore node_modules by default
const ignoreStrings = context.settings['import/ignore']
Expand Down
25 changes: 23 additions & 2 deletions src/core/parse.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import moduleRequire from './module-require'
import assign from 'object-assign'
import { extname } from 'path'
import debug from 'debug'

export default function (content, context) {
const log = debug('eslint-plugin-import:parse')

export default function (path, content, context) {

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

let { parserOptions, parserPath } = context
let { parserOptions } = context
const parserPath = getParserPath(path, context)

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

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

return parser.parse(content, parserOptions)
}

function getParserPath(path, context) {
const parsers = context.settings['import/parsers']
if (parsers != null) {
const extension = extname(path)
for (let parserPath in parsers) {
if (parsers[parserPath].indexOf(extension) > -1) {
// use this alternate parser
log('using alt parser:', parserPath)
return parserPath
}
}
}
// default to use ESLint parser
return context.parserPath
}
6 changes: 3 additions & 3 deletions tests/files/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type X = { y: string | null }
type X = string

export function getX() : X {
return null
export function getFoo() : X {
return "foo"
}
40 changes: 37 additions & 3 deletions tests/src/core/getExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ describe('getExports', function () {
var imports = ExportMap.parse(
path,
contents,
{ parserPath: 'babel-eslint' }
{ parserPath: 'babel-eslint', settings: {} }
)

expect(imports).to.exist
expect(imports.get('default')).to.exist
expect(imports, 'imports').to.exist
expect(imports.errors).to.be.empty
expect(imports.get('default'), 'default export').to.exist
expect(imports.has('Bar')).to.be.true
})

Expand Down Expand Up @@ -187,6 +188,7 @@ describe('getExports', function () {
sourceType: 'module',
attachComment: true,
},
settings: {},
})
})

Expand All @@ -197,6 +199,7 @@ describe('getExports', function () {
sourceType: 'module',
attachComment: true,
},
settings: {},
})
})
})
Expand Down Expand Up @@ -307,4 +310,35 @@ describe('getExports', function () {

})

context('alternate parsers', function () {
const configs = [
// ['string form', { 'typescript-eslint-parser': '.ts' }],
['array form', { 'typescript-eslint-parser': ['.ts', '.tsx'] }],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason that the string form one is commented out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started with just one string, then made it support both a string and an array.

Then I decided I was making it too complicated.

Do you think it would be better to support both?

]

configs.forEach(([description, parserConfig]) => {
describe(description, function () {
const context = Object.assign({}, fakeContext,
{ settings: {
'import/extensions': ['.js'],
'import/parsers': parserConfig,
} })

let imports
before('load imports', function () {
imports = ExportMap.get('./typescript.ts', context)
})

it('returns something for a TypeScript file', function () {
expect(imports).to.exist
})

it('has export (getFoo)', function () {
expect(imports.has('getFoo')).to.be.true
})
})
})

})

})
11 changes: 7 additions & 4 deletions tests/src/core/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import parse from 'core/parse'
import { getFilename } from '../utils'

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

before((done) =>
fs.readFile(getFilename('jsx.js'), { encoding: 'utf8' },
fs.readFile(path, { encoding: 'utf8' },
(err, f) => { if (err) { done(err) } else { content = f; done() }}))

it("doesn't support JSX by default", function () {
expect(() => parse(content, { parserPath: 'espree' })).to.throw(Error)
it('doesn\'t support JSX by default', function () {
expect(() => parse(path, content, { parserPath: 'espree' })).to.throw(Error)
})

it('infers jsx from ecmaFeatures when using stock parser', function () {
expect(() => parse(content, { parserPath: 'espree', parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true } } }))
expect(() => parse(path, content, { settings: {}, parserPath: 'espree', parserOptions: { sourceType: 'module', ecmaFeatures: { jsx: true } } }))
.not.to.throw(Error)
})

})