Skip to content

Release 2.11.0 #1071

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

Merged
merged 20 commits into from
Apr 12, 2018
Merged
Show file tree
Hide file tree
Changes from 17 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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]


# [2.11.0] - 2018-04-09
### Added
- Fixer for [`first`] ([#1046], thanks [@fengkfengk])
- `allow-require` option for [`no-commonjs`] rule ([#880], thanks [@futpib])

## [2.10.0] - 2018-03-29
### Added
- Autofixer for [`order`] rule ([#711], thanks [@tihonove])
Expand Down Expand Up @@ -452,9 +457,11 @@ for info on changes for earlier releases.

[`memo-parser`]: ./memo-parser/README.md

[#1046]: https://github.com/benmosher/eslint-plugin-import/pull/1046
[#944]: https://github.com/benmosher/eslint-plugin-import/pull/944
[#891]: https://github.com/benmosher/eslint-plugin-import/pull/891
[#889]: https://github.com/benmosher/eslint-plugin-import/pull/889
[#880]: https://github.com/benmosher/eslint-plugin-import/pull/880
[#858]: https://github.com/benmosher/eslint-plugin-import/pull/858
[#843]: https://github.com/benmosher/eslint-plugin-import/pull/843
[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871
Expand Down Expand Up @@ -589,7 +596,8 @@ for info on changes for earlier releases.
[#119]: https://github.com/benmosher/eslint-plugin-import/issues/119
[#89]: https://github.com/benmosher/eslint-plugin-import/issues/89

[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.10.0...HEAD
[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.11.0...HEAD
[2.11.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.10.0...v2.11.0
[2.10.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.9.0...v2.10.0
[2.9.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.8.0...v2.9.0
[2.8.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.7.0...v2.8.0
Expand Down Expand Up @@ -692,3 +700,5 @@ for info on changes for earlier releases.
[@isiahmeadows]: https://github.com/isiahmeadows
[@graingert]: https://github.com/graingert
[@danny-andrews]: https://github.com/dany-andrews
[@fengkfengk]: https://github.com/fengkfengk
[@futpib]: https://github.com/futpib
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
* Forbid import of modules using absolute paths ([`no-absolute-path`])
* Forbid `require()` calls with expressions ([`no-dynamic-require`])
* Prevent importing the submodules of other modules ([`no-internal-modules`])
* Forbid Webpack loader syntax in imports ([`no-webpack-loader-syntax`])
* Forbid webpack loader syntax in imports ([`no-webpack-loader-syntax`])
* Forbid a module from importing itself ([`no-self-import`])
* Forbid a module from importing a module with a dependency path back to itself ([`no-cycle`])

Expand Down Expand Up @@ -145,14 +145,14 @@ to find the file behind `module`.
Up through v0.10ish, this plugin has directly used substack's [`resolve`] plugin,
which implements Node's import behavior. This works pretty well in most cases.

However, Webpack allows a number of things in import module source strings that
However, webpack allows a number of things in import module source strings that
Node does not, such as loaders (`import 'file!./whatever'`) and a number of
aliasing schemes, such as [`externals`]: mapping a module id to a global name at
runtime (allowing some modules to be included more traditionally via script tags).

In the interest of supporting both of these, v0.11 introduces resolvers.

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

You can reference resolvers in several ways (in order of precedence):
Expand Down Expand Up @@ -218,7 +218,7 @@ If you are interesting in writing a resolver, see the [spec](./resolvers/README.
[`externals`]: http://webpack.github.io/docs/library-and-externals.html

[Node]: https://www.npmjs.com/package/eslint-import-resolver-node
[Webpack]: https://www.npmjs.com/package/eslint-import-resolver-webpack
[webpack]: https://www.npmjs.com/package/eslint-import-resolver-webpack

# Settings

Expand Down Expand Up @@ -303,7 +303,7 @@ An array of folders. Resolved modules only from those folders will be considered
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:
directly using webpack, for example:

```yaml
# .eslintrc.yml
Expand Down
6 changes: 6 additions & 0 deletions docs/rules/first.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ mode](http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code) so i

Given that, see [#255] for the reasoning.

### With Fixer

This rule contains a fixer to reorder in-body import to top, the following criteria applied:
1. Never re-order relative to each other, even if `absolute-first` is set.
2. If an import creates an identifier, and that identifier is referenced at module level *before* the import itself, that won't be re-ordered.

## When Not To Use It

If you don't mind imports being sprinkled throughout, you may not want to
Expand Down
25 changes: 22 additions & 3 deletions docs/rules/no-commonjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,29 @@ module.exports = { a: "b" }
exports.c = "d"
```

If `allow-primitive-modules` is provided as an option, the following is valid:
### Allow require

If `allowRequire` option is set to `true`, `require` calls are valid:

```js
/*eslint no-commonjs: [2, { allowRequire: true }]*/

if (typeof window !== "undefined") {
require('that-ugly-thing');
}
```

but `module.exports` is reported as usual.

This is useful for conditional requires.
If you don't rely on synchronous module loading, check out [dynamic import](https://github.com/airbnb/babel-plugin-dynamic-import-node).

### Allow primitive modules

If `allowPrimitiveModules` option is set to `true`, the following is valid:

```js
/*eslint no-commonjs: [2, "allow-primitive-modules"]*/
/*eslint no-commonjs: [2, { allowPrimitiveModules: true }]*/

module.exports = "foo"
module.exports = function rule(context) { return { /* ... */ } }
Expand All @@ -33,7 +52,7 @@ module.exports = function rule(context) { return { /* ... */ } }
but this is still reported:

```js
/*eslint no-commonjs: [2, "allow-primitive-modules"]*/
/*eslint no-commonjs: [2, { allowPrimitiveModules: true }]*/

module.exports = { x: "y" }
exports.z = function boop() { /* ... */ }
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/no-cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ cycles created by `require` within imported modules may not be detected.
There is a `maxDepth` option available to prevent full expansion of very deep dependency trees:

```js
/*eslint import/no-unresolved: [2, { maxDepth: 1 }]*/
/*eslint import/no-cycle: [2, { maxDepth: 1 }]*/

// dep-c.js
import './dep-a.js'
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-import",
"version": "2.10.0",
"version": "2.11.0",
"description": "Import with sanity.",
"engines": {
"node": ">=4"
Expand Down Expand Up @@ -79,7 +79,6 @@
"eslint": "2.x - 4.x"
},
"dependencies": {
"builtin-modules": "^1.1.1",
"contains-path": "^0.1.0",
"debug": "^2.6.8",
"doctrine": "1.5.0",
Expand All @@ -88,7 +87,8 @@
"has": "^1.0.1",
"lodash": "^4.17.4",
"minimatch": "^3.0.3",
"read-pkg-up": "^2.0.0"
"read-pkg-up": "^2.0.0",
"resolve": "^1.6.0"
},
"nyc": {
"require": [
Expand Down
4 changes: 2 additions & 2 deletions src/core/importType.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cond from 'lodash/cond'
import builtinModules from 'builtin-modules'
import coreModules from 'resolve/lib/core'
import { join } from 'path'

import resolve from 'eslint-module-utils/resolve'
Expand All @@ -24,7 +24,7 @@ export function isAbsolute(name) {
export function isBuiltIn(name, settings) {
const base = baseModule(name)
const extras = (settings && settings['import/core-modules']) || []
return builtinModules.indexOf(base) !== -1 || extras.indexOf(base) > -1
return coreModules[base] || extras.indexOf(base) > -1
}

function isExternalPath(path, name, settings) {
Expand Down
76 changes: 73 additions & 3 deletions src/rules/first.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
docs: {
url: docsUrl('first'),
},
fixable: 'code',
},

create: function (context) {
Expand All @@ -18,10 +19,17 @@ module.exports = {
'Program': function (n) {
const body = n.body
, absoluteFirst = context.options[0] === 'absolute-first'
, message = 'Import in body of module; reorder to top.'
, sourceCode = context.getSourceCode()
, originSourceCode = sourceCode.getText()
let nonImportCount = 0
, anyExpressions = false
, anyRelative = false
body.forEach(function (node){
, lastLegalImp = null
, errorInfos = []
, shouldSort = true
, lastSortNodesIndex = 0
body.forEach(function (node, index){
if (!anyExpressions && isPossibleDirective(node)) {
return
}
Expand All @@ -40,15 +48,77 @@ module.exports = {
}
}
if (nonImportCount > 0) {
context.report({
for (let variable of context.getDeclaredVariables(node)) {
if (!shouldSort) break
const references = variable.references
if (references.length) {
for (let reference of references) {
if (reference.identifier.range[0] < node.range[1]) {
shouldSort = false
break
}
}
}
}
shouldSort && (lastSortNodesIndex = errorInfos.length)
errorInfos.push({
node,
message: 'Import in body of module; reorder to top.',
range: [body[index - 1].range[1], node.range[1]],
})
} else {
lastLegalImp = node
}
} else {
nonImportCount++
}
})
if (!errorInfos.length) return
errorInfos.forEach(function (errorInfo, index) {
const node = errorInfo.node
, infos = {
node,
message,
}
if (index < lastSortNodesIndex) {
infos.fix = function (fixer) {
return fixer.insertTextAfter(node, '')
}
} else if (index === lastSortNodesIndex) {
const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1)
infos.fix = function (fixer) {
const removeFixers = sortNodes.map(function (_errorInfo) {
return fixer.removeRange(_errorInfo.range)
})
, range = [0, removeFixers[removeFixers.length - 1].range[1]]
let insertSourceCode = sortNodes.map(function (_errorInfo) {
const nodeSourceCode = String.prototype.slice.apply(
originSourceCode, _errorInfo.range
)
if (/\S/.test(nodeSourceCode[0])) {
return '\n' + nodeSourceCode
}
return nodeSourceCode
}).join('')
, insertFixer = null
, replaceSourceCode = ''
if (!lastLegalImp) {
insertSourceCode =
insertSourceCode.trim() + insertSourceCode.match(/^(\s+)/)[0]
}
insertFixer = lastLegalImp ?
fixer.insertTextAfter(lastLegalImp, insertSourceCode) :
fixer.insertTextBefore(body[0], insertSourceCode)
const fixers = [insertFixer].concat(removeFixers)
fixers.forEach(function (computedFixer, i) {
replaceSourceCode += (originSourceCode.slice(
fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0]
) + computedFixer.text)
})
return fixer.replaceTextRange(range, replaceSourceCode)
}
}
context.report(infos)
})
},
}
},
Expand Down
44 changes: 41 additions & 3 deletions src/rules/no-commonjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,69 @@ import docsUrl from '../docsUrl'
const EXPORT_MESSAGE = 'Expected "export" or "export default"'
, IMPORT_MESSAGE = 'Expected "import" instead of "require()"'

function allowPrimitive(node, context) {
if (context.options.indexOf('allow-primitive-modules') < 0) return false
function normalizeLegacyOptions(options) {
if (options.indexOf('allow-primitive-modules') >= 0) {
return { allowPrimitiveModules: true }
}
return options[0] || {}
}

function allowPrimitive(node, options) {
if (!options.allowPrimitiveModules) return false
if (node.parent.type !== 'AssignmentExpression') return false
return (node.parent.right.type !== 'ObjectExpression')
}

function allowRequire(node, options) {
return options.allowRequire
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const schemaString = { enum: ['allow-primitive-modules'] }
const schemaObject = {
type: 'object',
properties: {
allowPrimitiveModules: { 'type': 'boolean' },
allowRequire: { 'type': 'boolean' },
},
additionalProperties: false,
}

module.exports = {
meta: {
docs: {
url: docsUrl('no-commonjs'),
},

schema: {
anyOf: [
{
type: 'array',
items: [schemaString],
additionalItems: false,
},
{
type: 'array',
items: [schemaObject],
additionalItems: false,
},
],
},
},

create: function (context) {
const options = normalizeLegacyOptions(context.options)

return {

'MemberExpression': function (node) {

// module.exports
if (node.object.name === 'module' && node.property.name === 'exports') {
if (allowPrimitive(node, context)) return
if (allowPrimitive(node, options)) return
context.report({ node, message: EXPORT_MESSAGE })
}

Expand Down Expand Up @@ -65,6 +101,8 @@ module.exports = {
if (module.type !== 'Literal') return
if (typeof module.value !== 'string') return

if (allowRequire(call, options)) return

// keeping it simple: all 1-string-arg `require` calls are reported
context.report({
node: call.callee,
Expand Down
2 changes: 1 addition & 1 deletion src/rules/no-cycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {

create: function (context) {
const myPath = context.getFilename()
if (myPath === '<text>') return // can't cycle-check a non-file
if (myPath === '<text>') return {} // can't cycle-check a non-file

const options = context.options[0] || {}
const maxDepth = options.maxDepth || Infinity
Expand Down
Loading