diff --git a/.babelrc b/.babelrc index 5427da412d..8621a8adfd 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,9 @@ { "presets": [ "es2015-loose" ], - "sourceMaps": "inline" + "sourceMaps": "inline", + "env": { + "test": { + "plugins": [ "istanbul" ] + } + } } diff --git a/.gitignore b/.gitignore index 4a326a364d..00d37364ef 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ node_modules # generated output lib/ +/.nyc_output/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3acaf2d3..572b33d0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,17 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [Unreleased] -## [1.10.1] - 2016-07-02 +## [1.10.3] - 2016-07-08 +### Fixed +- removing `Symbol` dependencies (i.e. `for-of` loops) due to Node 0.10 polyfill + issue (see [#415]). Should not make any discernible semantic difference. + +## [1.10.2] - 2016-07-04 +### Fixed +- Something horrible happened during `npm prepublish` of 1.10.1. + Several `rm -rf node_modules && npm i` and `gulp clean && npm prepublish`s later, it is rebuilt and republished as 1.10.2. Thanks [@rhettlivingston] for noticing and reporting! + +## [1.10.1] - 2016-07-02 [YANKED] ### Added - Officially support ESLint 3.x. (peerDependencies updated to `2.x - 3.x`) @@ -275,6 +285,7 @@ for info on changes for earlier releases. [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 [#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 +[#415]: https://github.com/benmosher/eslint-plugin-import/issues/415 [#386]: https://github.com/benmosher/eslint-plugin-import/issues/386 [#373]: https://github.com/benmosher/eslint-plugin-import/issues/373 [#370]: https://github.com/benmosher/eslint-plugin-import/issues/370 @@ -302,7 +313,9 @@ 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/v1.10.1...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.3...HEAD +[1.10.3]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.2...v1.10.3 +[1.10.2]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.1...v1.10.2 [1.10.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.10.0...v1.10.1 [1.10.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.9.2...v1.10.0 [1.9.2]: https://github.com/benmosher/eslint-plugin-import/compare/v1.9.1...v1.9.2 @@ -351,3 +364,4 @@ for info on changes for earlier releases. [@scottnonnenberg]: https://github.com/scottnonnenberg [@sindresorhus]: https://github.com/sindresorhus [@ljharb]: https://github.com/ljharb +[@rhettlivingston]: https://github.com/rhettlivingston diff --git a/appveyor.yml b/appveyor.yml index 96fe93213e..288932b48b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -38,5 +38,8 @@ test_script: - cd .\resolvers\webpack && npm test && cd ..\.. - cd .\resolvers\node && npm test && cd ..\.. +on_success: + - npm run coveralls + # Don't actually build. build: off diff --git a/docs/rules/imports-first.md b/docs/rules/imports-first.md index c358cfa5df..0d5c762f56 100644 --- a/docs/rules/imports-first.md +++ b/docs/rules/imports-first.md @@ -1,7 +1,7 @@ # imports-first This rule reports any imports that come after non-import -statments. +statements. ## Rule Details @@ -24,6 +24,8 @@ import bar from './bar' import * as _ from 'lodash' // <- reported ``` +If you really want import type ordering, check out [`import/order`]. + Notably, `import`s are hoisted, which means the imported modules will be evaluated before any of the statements interspersed between them. Keeping all `import`s together at the top of the file may prevent surprises resulting from this part of the spec. @@ -55,6 +57,8 @@ enable this rule. ## Further Reading +- [`import/order`]: a major step up from `absolute-first` - Issue [#255] +[`import/order`]: ./order.md [#255]: https://github.com/benmosher/eslint-plugin-import/issues/255 diff --git a/docs/rules/no-duplicates.md b/docs/rules/no-duplicates.md index 0a7890472e..86260a6ff4 100644 --- a/docs/rules/no-duplicates.md +++ b/docs/rules/no-duplicates.md @@ -2,11 +2,19 @@ Reports if a resolved path is imported more than once. +ESLint core has a similar rule ([`no-duplicate-imports`](http://eslint.org/docs/rules/no-duplicate-imports)), but this version +is different in two key ways: + +1. the paths in the source code don't have to exactly match, they just have to point to the same module on the filesystem. (i.e. `./foo` and `./foo.js`) +2. this version distinguishes Flow `type` imports from standard imports. ([#334](https://github.com/benmosher/eslint-plugin-import/pull/334)) + ## Rule Details Valid: ```js import SomeDefaultClass, * as names from './mod' +// Flow `type` import from same module is fine +import type SomeType from './mod' ``` ...whereas here, both `./mod` imports will be reported: @@ -18,6 +26,9 @@ import SomeDefaultClass from './mod' import foo from './some-other-mod' import * as names from './mod' + +// will catch this too, assuming it is the same target module +import { something } from './mod.js' ``` The motivation is that this is likely a result of two developers importing different @@ -26,5 +37,7 @@ locations in the file.) This rule brings both (or n-many) to attention. ## When Not To Use It +If the core ESLint version is good enough (i.e. you're _not_ using Flow and you _are_ using [`import/extensions`](./extensions.md)), keep it and don't use this. + If you like to split up imports across lines or may need to import a default and a namespace, -you may want to disable this rule. +you may not want to enable this rule. diff --git a/gulpfile.js b/gulpfile.js index 74a3eaaeae..0fe6be4ba8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,102 +1,18 @@ var gulp = require('gulp') - , changed = require('gulp-changed') , babel = require('gulp-babel') - , mocha = require('gulp-mocha') - , path = require('path') - , glob = require('glob') - , fs = require('fs') , rimraf = require('rimraf') var SRC = 'src/**/*.js' , DEST = 'lib' -gulp.task('src', function () { +gulp.task('src', ['clean'], function () { return gulp.src(SRC) - .pipe(changed(DEST)) .pipe(babel()) .pipe(gulp.dest(DEST)) }) -/** - * Delete any file under `dest` that has no corresponding file in `src`. - * I.E. remove generated files that have been orphaned via deletion of their source. - * @param {string} src - * @param {string} dest - * @param {Function} done - callback upon completion - */ -function wipeExtras(src, dest, done) { - // glob into 'lib' and delete whatever isn't there - glob(dest + '/**/*.js', function (err, files) { - if (err) { - done(err); return - } - - function checkFile(index) { - if (index >= files.length) { - done(); return - } - - var libFilename = files[index] - , srcFilename = path.resolve(src, path.relative(path.resolve(dest), libFilename)) - - fs.stat(srcFilename, function (err) { - if (err) { - fs.unlink(libFilename, function () { - checkFile(index + 1) - }) - } else { - checkFile(index + 1) - } - }) - } - - - checkFile(0) - }) -} - -gulp.task('clean-src', function (done) { +gulp.task('clean', function (done) { rimraf(DEST, done) }) -gulp.task('clean-tests', function (done) { - rimraf('tests/lib', done) -}) - -gulp.task('clean', ['clean-src', 'clean-tests']) - -gulp.task('wipe-extras', function (done) { - var unfinished = 2 - function megadone(err) { - if (err) { done(err); return } - if (--unfinished === 0) done() - } - wipeExtras('src', DEST, megadone) - wipeExtras('tests/src', 'tests/lib', megadone) -}) - -gulp.task('prepublish', ['src', 'wipe-extras']) - -gulp.task('tests', function () { - return gulp.src('tests/src/**/*.js') - .pipe(changed('tests/lib')) - .pipe(babel()) - .pipe(gulp.dest('tests/lib')) -}) - -// used externally by Istanbul, too -gulp.task('pretest', ['src', 'tests', 'wipe-extras']) - -var reporter = 'spec' - -gulp.task('test', ['pretest'], function () { - return gulp.src('tests/lib/**/*.js', { read: false }) - .pipe(mocha({ reporter: reporter, grep: process.env.TEST_GREP, timeout: 5000 })) - // NODE_PATH=./lib mocha --recursive --reporter dot tests/lib/ -}) - -gulp.task('watch-test', function () { - reporter = 'progress' - gulp.watch(SRC, ['test']) - gulp.watch('tests/' + SRC, ['test']) -}) +gulp.task('prepublish', ['src']) diff --git a/package.json b/package.json index 252cb72105..2e45a5954a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "1.10.2", + "version": "1.10.3", "description": "Import with sanity.", "main": "lib/index.js", "directories": { @@ -15,12 +15,13 @@ "watch": "cross-env NODE_PATH=./lib gulp watch-test", "cover": "gulp pretest && cross-env NODE_PATH=./lib istanbul cover --dir reports/coverage _mocha tests/lib/ -- --recursive -R progress", "posttest": "eslint ./src", - "test": "cross-env NODE_PATH=./lib gulp test", + "test": "cross-env BABEL_ENV=test NODE_PATH=./src nyc mocha --recursive tests/src -R progress -t 5s", + "coverage-report": "npm t && nyc report --reporter html", "test-all": "npm test && for resolver in ./resolvers/*; do cd $resolver && npm test && cd ../..; done", "ci-test": "eslint ./src && gulp pretest && cross-env NODE_PATH=./lib istanbul cover --report lcovonly --dir reports/coverage _mocha tests/lib/ -- --recursive --reporter dot", "debug": "cross-env NODE_PATH=./lib mocha debug --recursive --reporter dot tests/lib/", "prepublish": "gulp prepublish", - "coveralls": "cat ./reports/coverage/lcov.info | coveralls" + "coveralls": "nyc report --reporter lcovonly && cat ./coverage/lcov.info | coveralls" }, "repository": { "type": "git", @@ -43,21 +44,21 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { "babel-eslint": "next", + "babel-plugin-istanbul": "^1.0.3", "babel-preset-es2015": "^6.6.0", "babel-preset-es2015-loose": "^7.0.0", + "babel-register": "6.9.0", "chai": "^3.4.0", "coveralls": "^2.11.4", "cross-env": "^1.0.7", - "eslint": "3.x", + "eslint": "2.x", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-webpack": "file:./resolvers/webpack", - "glob": "^6.0.2", "gulp": "^3.9.0", "gulp-babel": "6.1.2", - "gulp-changed": "^1.3.0", - "gulp-mocha": "^2.2.0", "istanbul": "^0.4.0", "mocha": "^2.2.1", + "nyc": "^7.0.0", "redux": "^3.0.4", "rimraf": "2.5.2" }, @@ -70,7 +71,6 @@ "doctrine": "1.2.x", "es6-map": "^0.1.3", "es6-set": "^0.1.4", - "es6-symbol": "*", "eslint-import-resolver-node": "^0.2.0", "lodash.cond": "^4.3.0", "lodash.endswith": "^4.0.1", @@ -79,5 +79,12 @@ "object-assign": "^4.0.1", "pkg-dir": "^1.0.0", "pkg-up": "^1.0.0" + }, + "nyc": { + "require": [ + "babel-register" + ], + "sourceMap": false, + "instrument": false } } diff --git a/src/core/getExports.js b/src/core/getExports.js index 00a0a273b8..ae2ca4d892 100644 --- a/src/core/getExports.js +++ b/src/core/getExports.js @@ -1,4 +1,3 @@ -import 'es6-symbol/implement' import Map from 'es6-map' import * as fs from 'fs' @@ -236,18 +235,19 @@ export default class ExportMap { if (this.reexports.has(name)) return true // default exports must be explicitly re-exported (#328) + let foundInnerMapName = false if (name !== 'default') { - for (let dep of this.dependencies.values()) { - let innerMap = dep() + this.dependencies.forEach((dep) => { + if (!foundInnerMapName) { + let innerMap = dep() - // todo: report as unresolved? - if (!innerMap) continue - - if (innerMap.has(name)) return true - } + // todo: report as unresolved? + if (innerMap && innerMap.has(name)) foundInnerMapName = true + } + }) } - return false + return foundInnerMapName } /** @@ -276,24 +276,29 @@ export default class ExportMap { // default exports must be explicitly re-exported (#328) + let returnValue = { found: false, path: [this] } if (name !== 'default') { - for (let dep of this.dependencies.values()) { - let innerMap = dep() - // todo: report as unresolved? - if (!innerMap) continue - - // safeguard against cycles - if (innerMap.path === this.path) continue - - let innerValue = innerMap.hasDeep(name) - if (innerValue.found) { - innerValue.path.unshift(this) - return innerValue + this.dependencies.forEach((dep) => { + if (!returnValue.found) { + let innerMap = dep() + // todo: report as unresolved? + if (innerMap) { + + // safeguard against cycles + if (innerMap.path !== this.path) { + + let innerValue = innerMap.hasDeep(name) + if (innerValue.found) { + innerValue.path.unshift(this) + returnValue = innerValue + } + } + } } - } + }) } - return { found: false, path: [this] } + return returnValue } get(name) { @@ -313,21 +318,26 @@ export default class ExportMap { } // default exports must be explicitly re-exported (#328) + let returnValue = undefined if (name !== 'default') { - for (let dep of this.dependencies.values()) { - let innerMap = dep() - // todo: report as unresolved? - if (!innerMap) continue - - // safeguard against cycles - if (innerMap.path === this.path) continue - - let innerValue = innerMap.get(name) - if (innerValue !== undefined) return innerValue - } + this.dependencies.forEach((dep) => { + if (returnValue === undefined) { + let innerMap = dep() + // todo: report as unresolved? + if (innerMap) { + + // safeguard against cycles + if (innerMap.path !== this.path) { + + let innerValue = innerMap.get(name) + if (innerValue !== undefined) returnValue = innerValue + } + } + } + }) } - return undefined + return returnValue } forEach(callback, thisArg) { diff --git a/src/core/resolve.js b/src/core/resolve.js index 1f561f948e..140633f25a 100644 --- a/src/core/resolve.js +++ b/src/core/resolve.js @@ -1,4 +1,3 @@ -import 'es6-symbol/implement' import Map from 'es6-map' import Set from 'es6-set' import assign from 'object-assign' @@ -112,23 +111,24 @@ function fullResolve(modulePath, sourceFile, settings) { const resolvers = resolverReducer(configResolvers, new Map()) - for (let [name, config] of resolvers) { - const resolver = requireResolver(name, sourceFile) - , resolved = withResolver(resolver, config) - - if (!resolved.found) continue - - // resolvers imply file existence, this double-check just ensures the case matches - if (!fileExistsWithCaseSync(resolved.path, cacheSettings)) continue - - // else, counts - cache(resolved.path) - return resolved - } + let resolved = { found: false } + resolvers.forEach(function (config, name) { + if (!resolved.found) { + const resolver = requireResolver(name, sourceFile) + resolved = withResolver(resolver, config) + if (resolved.found) { + // resolvers imply file existence, this double-check just ensures the case matches + if (fileExistsWithCaseSync(resolved.path, cacheSettings)) { + // else, counts + cache(resolved.path) + } else { + resolved = { found: false } + } + } + } + }) - // failed - // cache(undefined) - return { found: false } + return resolved } function resolverReducer(resolvers, map) { diff --git a/src/rules/export.js b/src/rules/export.js index 9263b0ae31..0f1eec415b 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -1,4 +1,3 @@ -import 'es6-symbol/implement' import Map from 'es6-map' import Set from 'es6-set' @@ -32,11 +31,11 @@ module.exports = function (context) { addNamed(node.declaration.id.name, node.declaration.id) } - if (node.declaration.declarations != null) { - for (let declaration of node.declaration.declarations) { - recursivePatternCapture(declaration.id, v => addNamed(v.name, v)) - } - } + if (node.declaration.declarations == null) return + + node.declaration.declarations.forEach(declaration => { + recursivePatternCapture(declaration.id, v => addNamed(v.name, v)) + }) }, 'ExportAllDeclaration': function (node) { @@ -62,15 +61,15 @@ module.exports = function (context) { }, 'Program:exit': function () { - for (let [name, nodes] of named) { - if (nodes.size <= 1) continue + named.forEach((nodes, name) => { + if (nodes.size <= 1) return - for (let node of nodes) { + nodes.forEach(node => { if (name === 'default') { context.report(node, 'Multiple default exports.') } else context.report(node, `Multiple exports of name '${name}'.`) - } - } + }) + }) }, } } diff --git a/src/rules/namespace.js b/src/rules/namespace.js index 32ae15a07b..9b4d375d46 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -1,4 +1,3 @@ -import 'es6-symbol/implement' import Map from 'es6-map' import Exports from '../core/getExports' @@ -32,7 +31,7 @@ module.exports = function (context) { return } - for (let specifier of declaration.specifiers) { + declaration.specifiers.forEach((specifier) => { switch (specifier.type) { case 'ImportNamespaceSpecifier': if (!imports.size) { @@ -51,7 +50,7 @@ module.exports = function (context) { break } } - } + }) } body.forEach(processBodyStatement) }, @@ -129,28 +128,23 @@ module.exports = function (context) { if (pattern.type !== 'ObjectPattern') return - for (let property of pattern.properties) { - + pattern.properties.forEach((property) => { if (property.key.type !== 'Identifier') { context.report({ node: property, message: 'Only destructure top-level names.', }) - continue - } - - if (!namespace.has(property.key.name)) { + } else if (!namespace.has(property.key.name)) { context.report({ node: property, message: makeMessage(property.key, path), }) - continue + } else { + path.push(property.key.name) + testKey(property.value, namespace.get(property.key.name).namespace, path) + path.pop() } - - path.push(property.key.name) - testKey(property.value, namespace.get(property.key.name).namespace, path) - path.pop() - } + }) } testKey(id, namespaces.get(init.name)) diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index fe6afb0943..1c573bd53a 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -1,17 +1,16 @@ -import 'es6-symbol/implement' import Map from 'es6-map' import Set from 'es6-set' import resolve from '../core/resolve' function checkImports(imported, context) { - for (let [module, nodes] of imported.entries()) { + imported.forEach((nodes, module) => { if (nodes.size > 1) { - for (let node of nodes) { + nodes.forEach((node) => { context.report(node, `'${module}' imported multiple times.`) - } + }) } - } + }) } module.exports = function (context) { diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index e6173bf11c..fc4cf2e2e8 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -7,15 +7,15 @@ module.exports = function (context) { } function checkDeclarationsInScope({variables}, name) { - for (let variable of variables) { + variables.forEach((variable) => { if (variable.name === name) { - for (let def of variable.defs) { + variable.defs.forEach((def) => { if (def.type === 'Variable') { checkDeclaration(def.parent) } - } + }) } - } + }) } function handleExportDefault(node) { @@ -32,9 +32,9 @@ module.exports = function (context) { if (node.declaration) { checkDeclaration(node.declaration) } else if (!node.source) { - for (let specifier of node.specifiers) { + node.specifiers.forEach((specifier) => { checkDeclarationsInScope(scope, specifier.local.name) - } + }) } } diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js index 36ae643c2e..ba102e3771 100644 --- a/src/rules/no-named-as-default-member.js +++ b/src/rules/no-named-as-default-member.js @@ -5,7 +5,6 @@ * See LICENSE in root directory for full license. */ -import 'es6-symbol/implement' import Map from 'es6-map' import Exports from '../core/getExports' @@ -57,10 +56,11 @@ module.exports = function(context) { if (!isDestructure) return const objectName = node.init.name - for (const { key } of node.id.properties) { - if (key == null) continue // true for rest properties - storePropertyLookup(objectName, key.name, key) - } + node.id.properties.forEach(({key}) => { + if (key != null) { // rest properties are null + storePropertyLookup(objectName, key.name, key) + } + }) } function handleProgramExit() { @@ -68,19 +68,19 @@ module.exports = function(context) { const fileImport = fileImports.get(objectName) if (fileImport == null) return - for (const {propName, node} of lookups) { - if (!fileImport.exportMap.namespace.has(propName)) continue - - context.report({ - node, - message: ( - `Caution: \`${objectName}\` also has a named export ` + - `\`${propName}\`. Check if you meant to write ` + - `\`import {${propName}} from '${fileImport.sourcePath}'\` ` + - 'instead.' - ), - }) - } + lookups.forEach(({propName, node}) => { + if (fileImport.exportMap.namespace.has(propName)) { + context.report({ + node, + message: ( + `Caution: \`${objectName}\` also has a named export ` + + `\`${propName}\`. Check if you meant to write ` + + `\`import {${propName}} from '${fileImport.sourcePath}'\` ` + + 'instead.' + ), + }) + } + }) }) } diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js index 8afd1613db..71b92a6da0 100644 --- a/src/rules/no-unresolved.js +++ b/src/rules/no-unresolved.js @@ -3,8 +3,6 @@ * @author Ben Mosher */ -import 'es6-symbol/implement' - import resolve from '../core/resolve' module.exports = function (context) { @@ -53,15 +51,17 @@ module.exports = function (context) { const modules = call.arguments[0] if (modules.type !== 'ArrayExpression') return - for (let element of modules.elements) { - if (element.type !== 'Literal') continue - if (typeof element.value !== 'string') continue - - if (element.value === 'require' || - element.value === 'exports') continue // magic modules: http://git.io/vByan + modules.elements.forEach((element) => { + if (element.type === 'Literal' && + typeof element.value === 'string') { - checkSourceValue(element) - } + // magic modules: http://git.io/vByan + if (element.value !== 'require' && + element.value !== 'exports') { + checkSourceValue(element) + } + } + }) } const visitors = { diff --git a/tests/src/cli.js b/tests/src/cli.js index c41791ae9f..93a4d43d79 100644 --- a/tests/src/cli.js +++ b/tests/src/cli.js @@ -11,7 +11,7 @@ describe('CLI regression tests', function () { cli = new CLIEngine({ useEslintrc: false, configFile: './tests/files/issue210.config.js', - rulePaths: ['./lib/rules'], + rulePaths: ['./src/rules'], rules: { 'named': 2, }, diff --git a/tests/src/package.js b/tests/src/package.js index 290a1f7a0e..f3648dd915 100644 --- a/tests/src/package.js +++ b/tests/src/package.js @@ -1,12 +1,10 @@ -import 'es6-symbol/implement' - var expect = require('chai').expect var path = require('path') , fs = require('fs') describe('package', function () { - let pkg = path.join(process.cwd(), 'lib') + let pkg = path.join(process.cwd(), 'src') , module before('is importable', function () { @@ -36,10 +34,10 @@ describe('package', function () { it('exports all configs', function (done) { fs.readdir(path.join(process.cwd(), 'config'), function (err, files) { if (err) { done(err); return } - for (let file of files) { - if (file[0] === '.') continue + files.forEach(file => { + if (file[0] === '.') return expect(module.configs).to.have.property(file.slice(0, -3)) // drop '.js' - } + }) done() }) })