diff --git a/.gitignore b/.gitignore index 24ceac8ff..0bf60608a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ node_modules # generated output lib/ **/.nyc_output/ + +# Only apps should have lockfiles +yarn.lock +package-lock.json +npm-shrinkwrap.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index 504c00e82..13e4afec8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,23 @@ language: node_js node_js: - 4 - 6 + - 8 os: - linux - osx +env: + - ESLINT_VERSION=2 + - ESLINT_VERSION=3 + - ESLINT_VERSION=4 + install: - - npm -g install npm@3 + - if [ ${TRAVIS_NODE_VERSION} == "4" ]; then + npm install -g npm@3; + fi - npm install + - npm install eslint@$ESLINT_VERSION --ignore-scripts || true # install all resolver deps - "for resolver in ./resolvers/*; do cd $resolver && npm install && cd ../..; done" diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ab2922d..25a6e3885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,41 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] + +### Fixed +- [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys]) + +## [2.7.0] - 2017-07-06 +### Changed +- [`no-absolute-path`] picks up speed boost, optional AMD support ([#843], thansk [@jseminck]) + +## [2.6.1] - 2017-06-29 +### Fixed +- update bundled node resolver dependency to latest version + +## [2.6.0] - 2017-06-23 +### Changed +- update tests / peerDeps for ESLint 4.0 compatibility ([#871], thanks [@mastilver]) +- [`memo-parser`] updated to require `filePath` on parser options as it melts + down if it's not there, now that this plugin always provides it. (see [#863]) + +## [2.5.0] - 2017-06-22 + +Re-releasing v[2.4.0] after discovering that the memory leak is isolated to the [`memo-parser`], +which is more or less experimental anyway. + +### Added +- Autofixer for newline-after-import. ([#686] + [#696], thanks [@eelyafi]) + +## [2.4.0] - 2017-06-02 [YANKED] + +Yanked due to critical issue in eslint-module-utils with cache key resulting from [#839]. + +### Added +- Add `filePath` into `parserOptions` passed to `parser` ([#839], thanks [@sompylasar]) +- Add `allow` option to [`no-unassigned-import`] to allow for files that match the globs ([#671], [#737], thanks [@kevin940726]). + +## [2.3.0] - 2017-05-18 ### Added - [`no-anonymous-default-export`] rule: report anonymous default exports ([#712], thanks [@duncanbeevers]). - Add new value to [`order`]'s `newlines-between` option to allow newlines inside import groups ([#627], [#628], thanks [@giodamelio]) @@ -11,11 +46,12 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Changed - [`no-extraneous-dependencies`]: use `read-pkg-up` to simplify finding + loading `package.json` ([#680], thanks [@wtgtybhertgeghgtwtg]) +- Add support to specify the package.json [`no-extraneous-dependencies`] ([#685], thanks [@ramasilveyra]) ### Fixed -- [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys]) - attempt to fix crash in [`no-mutable-exports`]. ([#660]) - "default is a reserved keyword" in no-maned-default tests by locking down babylon to 6.15.0 (#756, thanks @gmathieu) +- support scoped modules containing non word characters ## [2.2.0] - 2016-11-07 @@ -384,10 +420,16 @@ for info on changes for earlier releases. [`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md [`unambiguous`]: ./docs/rules/unambiguous.md [`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md +[`memo-parser`]: ./memo-parser/README.md [#794]: https://github.com/benmosher/eslint-plugin-import/pull/794 +[#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 +[#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 [#742]: https://github.com/benmosher/eslint-plugin-import/pull/742 +[#737]: https://github.com/benmosher/eslint-plugin-import/pull/737 [#712]: https://github.com/benmosher/eslint-plugin-import/pull/712 +[#696]: https://github.com/benmosher/eslint-plugin-import/pull/696 +[#685]: https://github.com/benmosher/eslint-plugin-import/pull/685 [#680]: https://github.com/benmosher/eslint-plugin-import/pull/680 [#654]: https://github.com/benmosher/eslint-plugin-import/pull/654 [#639]: https://github.com/benmosher/eslint-plugin-import/pull/639 @@ -443,6 +485,10 @@ for info on changes for earlier releases. [#314]: https://github.com/benmosher/eslint-plugin-import/pull/314 [#793]: https://github.com/benmosher/eslint-plugin-import/issues/793 +[#863]: https://github.com/benmosher/eslint-plugin-import/issues/863 +[#839]: https://github.com/benmosher/eslint-plugin-import/issues/839 +[#686]: https://github.com/benmosher/eslint-plugin-import/issues/686 +[#671]: https://github.com/benmosher/eslint-plugin-import/issues/671 [#660]: https://github.com/benmosher/eslint-plugin-import/issues/660 [#653]: https://github.com/benmosher/eslint-plugin-import/issues/653 [#627]: https://github.com/benmosher/eslint-plugin-import/issues/627 @@ -500,7 +546,13 @@ 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.2.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v2.7.0...HEAD +[2.7.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.6.1...v2.7.0 +[2.6.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.6.0...v2.6.1 +[2.6.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.5.0...v2.6.0 +[2.5.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.4.0...v2.5.0 +[2.4.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.3.0...v2.4.0 +[2.3.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.2.0...v2.3.0 [2.2.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.1.0...v2.2.0 [2.1.0]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.1...v2.1.0 [2.0.1]: https://github.com/benmosher/eslint-plugin-import/compare/v2.0.0...v2.0.1 @@ -578,4 +630,10 @@ for info on changes for earlier releases. [@duncanbeevers]: https://github.com/duncanbeevers [@giodamelio]: https://github.com/giodamelio [@ntdb]: https://github.com/ntdb +[@ramasilveyra]: https://github.com/ramasilveyra +[@sompylasar]: https://github.com/sompylasar +[@kevin940726]: https://github.com/kevin940726 +[@eelyafi]: https://github.com/eelyafi +[@mastilver]: https://github.com/mastilver +[@jseminck]: https://github.com/jseminck [@ephys]: https://github.com/ephys diff --git a/README.md b/README.md index c8c3789aa..aec72d44a 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ In the interest of supporting both of these, v0.11 introduces resolvers. 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): +You can reference resolvers in several ways (in order of precedence): - as a conventional `eslint-import-resolver` name, like `eslint-import-resolver-foo`: diff --git a/RELEASE.md b/RELEASE.md index fa14a18f5..e16a58993 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -26,8 +26,7 @@ ``` Generally, don't use `npm version` for this because it creates a tag, which I normally - wait until signoff from all contributors (`new Set(["@jfmengels"])`) and actually - `npm publish`-ing to snap the tag. + wait until signoff from contributors and actually `npm publish`-ing to snap the tag. 3. create pull request from `release-[x.y.z]` into `release` branch diff --git a/appveyor.yml b/appveyor.yml index 288932b48..406099f66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,8 +1,9 @@ # Test against this version of Node.js environment: matrix: - - nodejs_version: "4" + - nodejs_version: "8" - nodejs_version: "6" + - nodejs_version: "4" # platform: # - x86 @@ -13,11 +14,11 @@ install: # Get the latest stable version of Node.js or io.js - ps: Install-Product node $env:nodejs_version - # update npm (only needed for Node 0.10) - - npm -g install npm@3 - # - set PATH=%APPDATA%\npm;%PATH% - # install modules + - ps: >- + if ($env:nodejs_version -eq "4") { + npm install -g npm@3; + } - npm install # todo: learn how to do this for all .\resolvers\* on Windows diff --git a/docs/rules/newline-after-import.md b/docs/rules/newline-after-import.md index 42e73f656..8c5d5760f 100644 --- a/docs/rules/newline-after-import.md +++ b/docs/rules/newline-after-import.md @@ -1,6 +1,7 @@ # newline-after-import Enforces having one or more empty lines after the last top-level import statement or require call. ++(fixable) The `--fix` option on the [command line] automatically fixes problems reported by this rule. ## Rule Details @@ -76,7 +77,7 @@ const FOO = 'BAR' { ... "rules": { - "import/newline-after-import": [{ "count": 2 }] + "import/newline-after-import": ["error", { "count": 2 }] } } ``` diff --git a/docs/rules/no-absolute-path.md b/docs/rules/no-absolute-path.md index 9a4be74f8..aa9cf142d 100644 --- a/docs/rules/no-absolute-path.md +++ b/docs/rules/no-absolute-path.md @@ -25,3 +25,24 @@ var _ = require('lodash'); var foo = require('foo'); var foo = require('./foo'); ``` + +### Options + +By default, only ES6 imports and CommonJS `require` calls will have this rule enforced. + +You may provide an options object providing true/false for any of + +- `esmodule`: defaults to `true` +- `commonjs`: defaults to `true` +- `amd`: defaults to `false` + +If `{ amd: true }` is provided, dependency paths for AMD-style `define` and `require` +calls will be resolved: + +```js +/*eslint import/no-absolute-path: [2, { commonjs: false, amd: true }]*/ +define(['/foo'], function (foo) { /*...*/ }) // reported +require(['/foo'], function (foo) { /*...*/ }) // reported + +const foo = require('/foo') // ignored because of explicit `commonjs: false` +``` diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md index adb532e39..36aef8f9d 100644 --- a/docs/rules/no-extraneous-dependencies.md +++ b/docs/rules/no-extraneous-dependencies.md @@ -1,7 +1,7 @@ # Forbid the use of extraneous packages Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies` or `peerDependencies`. -The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. +The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behaviour can be changed with the rule option `packageDir`. ### Options @@ -25,7 +25,13 @@ You can also use an array of globs instead of literal booleans: "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.test.js", "**/*.spec.js"]}] ``` -When using an array of globs, the setting will be activated if the name of the file being linted matches a single glob in the array. +When using an array of globs, the setting will be set to `true` (no errors reported) if the name of the file being linted matches a single glob in the array, and `false` otherwise. + +Also there is one more option called `packageDir`, this option is to specify the path to the folder containing package.json and is relative to the current working directory. + +```js +"import/no-extraneous-dependencies": ["error", {"packageDir": './some-dir/'}] +``` ## Rule Details @@ -83,7 +89,7 @@ var foo = require('./foo'); import test from 'ava'; import find from 'lodash.find'; -import find from 'lodash.isarray'; +import isArray from 'lodash.isarray'; /* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */ import react from 'react'; diff --git a/docs/rules/no-unassigned-import.md b/docs/rules/no-unassigned-import.md index 1b25b0169..85f9b7c3a 100644 --- a/docs/rules/no-unassigned-import.md +++ b/docs/rules/no-unassigned-import.md @@ -6,11 +6,24 @@ With both CommonJS' `require` and the ES6 modules' `import` syntax, it is possib This rule aims to remove modules with side-effects by reporting when a module is imported but not assigned. +### Options + +This rule supports the following option: + +`allow`: An Array of globs. The files that match any of these patterns would be ignored/allowed by the linter. This can be useful for some build environments (e.g. css-loader in webpack). + +Note that the globs start from the where the linter is executed (usually project root), but not from each file that includes the source. Learn more in both the pass and fail examples below. + + ## Fail ```js import 'should' require('should') + +// In /src/app.js +import '../styles/app.css' +// {"allow": ["styles/*.css"]} ``` @@ -34,4 +47,13 @@ bar(require('foo')) require('foo').bar require('foo').bar() require('foo')() + +// With allow option set +import './style.css' // {"allow": ["**/*.css"]} +import 'babel-register' // {"allow": ["babel-register"]} + +// In /src/app.js +import './styles/app.css' +import '../scripts/register.js' +// {"allow": ["src/styles/**", "**/scripts/*.js"]} ``` diff --git a/memo-parser/index.js b/memo-parser/index.js index d8296ac37..9fd74c33a 100644 --- a/memo-parser/index.js +++ b/memo-parser/index.js @@ -17,9 +17,12 @@ const parserOptions = { } exports.parse = function parse(content, options) { - // them defaults yo options = Object.assign({}, options, parserOptions) + if (!options.filePath) { + throw new Error("no file path provided!") + } + const keyHash = crypto.createHash('sha256') keyHash.update(content) hashObject(options, keyHash) diff --git a/memo-parser/package.json b/memo-parser/package.json index a1ffae1ca..fa7d12973 100644 --- a/memo-parser/package.json +++ b/memo-parser/package.json @@ -1,7 +1,9 @@ { "name": "memo-parser", - "version": "0.1.0", - "engines": { "node": ">=4" }, + "version": "0.2.0", + "engines": { + "node": ">=4" + }, "description": "Memoizing wrapper for any ESLint-compatible parser module.", "main": "index.js", "scripts": { @@ -21,5 +23,8 @@ "bugs": { "url": "https://github.com/benmosher/eslint-plugin-import/issues" }, - "homepage": "https://github.com/benmosher/eslint-plugin-import#readme" + "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", + "peerDependencies": { + "eslint": ">=3.5.0" + } } diff --git a/package.json b/package.json index 6566f0952..c132f27dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "2.2.0", + "version": "2.7.0", "description": "Import with sanity.", "engines": { "node": ">=4" @@ -51,12 +51,12 @@ "babel-eslint": "next", "babel-plugin-istanbul": "^2.0.1", "babel-preset-es2015-argon": "latest", - "babel-register": "6.16.3", + "babel-register": "6.24.1", "babylon": "6.15.0", "chai": "^3.4.0", "coveralls": "^2.11.4", - "cross-env": "^3.1.0", - "eslint": "3.x", + "cross-env": "^4.0.0", + "eslint": "2.x - 4.x", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-module-utils": "file:./utils", @@ -70,19 +70,20 @@ "nyc": "^8.3.0", "redux": "^3.0.4", "rimraf": "2.5.2", + "sinon": "^2.3.2", "typescript": "^2.0.3", - "typescript-eslint-parser": "^1.0.0" + "typescript-eslint-parser": "^2.1.0" }, "peerDependencies": { - "eslint": "2.x - 3.x" + "eslint": "2.x - 4.x" }, "dependencies": { "builtin-modules": "^1.1.1", "contains-path": "^0.1.0", - "debug": "^2.2.0", + "debug": "^2.6.8", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.2.0", - "eslint-module-utils": "^2.0.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.1.1", "has": "^1.0.1", "lodash.cond": "^4.3.0", "minimatch": "^3.0.3", diff --git a/resolvers/node/CHANGELOG.md b/resolvers/node/CHANGELOG.md index 78c6f77b1..ab692d34b 100644 --- a/resolvers/node/CHANGELOG.md +++ b/resolvers/node/CHANGELOG.md @@ -6,6 +6,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## v0.3.1 - 2017-06-23 +### Changed +- bumped `debug` dep to match other packages + ## v0.3.0 - 2016-12-15 ### Changed - bumped `resolve` to fix issues with Node builtins (thanks [@SkeLLLa] and [@ljharb]) diff --git a/resolvers/node/package.json b/resolvers/node/package.json index ddd1e5061..cce7be4f2 100644 --- a/resolvers/node/package.json +++ b/resolvers/node/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-node", - "version": "0.3.0", + "version": "0.3.1", "description": "Node default behavior import resolution plugin for eslint-plugin-import.", "main": "index.js", "files": ["index.js"], @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import", "dependencies": { - "debug": "^2.2.0", + "debug": "^2.6.8", "resolve": "^1.2.0" }, "devDependencies": { diff --git a/resolvers/webpack/CHANGELOG.md b/resolvers/webpack/CHANGELOG.md index 6f1f5efef..7b8f9a662 100644 --- a/resolvers/webpack/CHANGELOG.md +++ b/resolvers/webpack/CHANGELOG.md @@ -5,6 +5,13 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +## 0.8.3 - 2017-06-23 +### Changed +- `debug` bumped to match others + +## 0.8.2 - 2017-06-22 +### Changed +- `webpack` peer dep updated to >= 1.11 (works fine with webpack 3 AFAICT) ## 0.8.1 - 2017-01-19 ### Changed diff --git a/resolvers/webpack/package.json b/resolvers/webpack/package.json index 2f3e06930..92bad18a8 100644 --- a/resolvers/webpack/package.json +++ b/resolvers/webpack/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-webpack", - "version": "0.8.1", + "version": "0.8.3", "description": "Resolve paths to dependencies, given a webpack.config.js. Plugin for eslint-plugin-import.", "main": "index.js", "scripts": { @@ -30,7 +30,7 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "dependencies": { "array-find": "^1.0.0", - "debug": "^2.2.0", + "debug": "^2.6.8", "enhanced-resolve": "~0.9.0", "find-root": "^0.1.1", "has": "^1.0.1", @@ -43,7 +43,7 @@ }, "peerDependencies": { "eslint-plugin-import": ">=1.4.0", - "webpack": "^1.11.0 || ^2.1.0-beta || ^2.1.0 || ^2.2.0-rc" + "webpack": ">=1.11.0" }, "devDependencies": { "chai": "^3.4.1", diff --git a/src/ExportMap.js b/src/ExportMap.js index a75ef44bc..e5e772914 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -151,8 +151,14 @@ export default class ExportMap { callback.call(thisArg, reexported && reexported.get(reexports.local), name, this) }) - this.dependencies.forEach(dep => dep().forEach((v, n) => - n !== 'default' && callback.call(thisArg, v, n, this))) + this.dependencies.forEach(dep => { + const d = dep() + // CJS / ignored dependencies won't exist (#717) + if (d == null) return + + d.forEach((v, n) => + n !== 'default' && callback.call(thisArg, v, n, this)) + }) } // todo: keys, values, entries? @@ -400,6 +406,7 @@ ExportMap.parse = function (path, content, context) { case 'FunctionDeclaration': case 'ClassDeclaration': case 'TypeAlias': // flowtype with babel-eslint parser + case 'InterfaceDeclaration': m.namespace.set(n.declaration.id.name, captureDoc(docStyleParsers, n)) break case 'VariableDeclaration': diff --git a/src/core/importType.js b/src/core/importType.js index 9f03b2f30..4e3c1db37 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -8,7 +8,7 @@ function constant(value) { return () => value } -function isAbsolute(name) { +export function isAbsolute(name) { return name.indexOf('/') === 0 } @@ -31,7 +31,7 @@ function isExternalModule(name, settings, path) { return externalModuleRegExp.test(name) && isExternalPath(path, name, settings) } -const scopedRegExp = /^@\w+\/\w+/ +const scopedRegExp = /^@[^/]+\/[^/]+/ function isScoped(name) { return scopedRegExp.test(name) } diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 868904ba3..579fe43ac 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -57,6 +57,7 @@ module.exports = { 'additionalProperties': false, }, ], + fixable: 'whitespace', }, create: function (context) { let level = 0 @@ -68,7 +69,10 @@ module.exports = { } const options = context.options[0] || { count: 1 } - if (getLineDifference(node, nextNode) < options.count + 1) { + const lineDifference = getLineDifference(node, nextNode) + const EXPECTED_LINE_DIFFERENCE = options.count + 1 + + if (lineDifference < EXPECTED_LINE_DIFFERENCE) { let column = node.loc.start.column if (node.loc.start.line !== node.loc.end.line) { @@ -81,6 +85,10 @@ module.exports = { column, }, message: `Expected empty line after ${type} statement not followed by another ${type}.`, + fix: fixer => fixer.insertTextAfter( + node, + '\n'.repeat(EXPECTED_LINE_DIFFERENCE - lineDifference) + ), }) } } diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js index 33da932fd..0f7b2bc48 100644 --- a/src/rules/no-absolute-path.js +++ b/src/rules/no-absolute-path.js @@ -1,27 +1,20 @@ -import importType from '../core/importType' -import isStaticRequire from '../core/staticRequire' - -function reportIfMissing(context, node, name) { - if (importType(name, context) === 'absolute') { - context.report(node, 'Do not import modules using an absolute path') - } -} +import { isAbsolute } from '../core/importType' +import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' module.exports = { meta: { docs: {}, + schema: [ makeOptionsSchema() ], }, create: function (context) { - return { - ImportDeclaration: function handleImports(node) { - reportIfMissing(context, node, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, node, node.arguments[0].value) - } - }, + function reportIfAbsolute(source) { + if (isAbsolute(source.value)) { + context.report(source, 'Do not import modules using an absolute path') + } } + + const options = Object.assign({ esmodule: true, commonjs: true }, context.options[0]) + return moduleVisitor(reportIfAbsolute, options) }, } diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 166d4f843..ce5041c97 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -1,16 +1,20 @@ import path from 'path' +import fs from 'fs' import readPkgUp from 'read-pkg-up' import minimatch from 'minimatch' import importType from '../core/importType' import isStaticRequire from '../core/staticRequire' -function getDependencies(context) { +function getDependencies(context, packageDir) { try { - const pkg = readPkgUp.sync({cwd: context.getFilename(), normalize: false}) - if (!pkg || !pkg.pkg) { + const packageContent = packageDir + ? JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) + : readPkgUp.sync({cwd: context.getFilename(), normalize: false}).pkg + + if (!packageContent) { return null } - const packageContent = pkg.pkg + return { dependencies: packageContent.dependencies || {}, devDependencies: packageContent.devDependencies || {}, @@ -18,6 +22,19 @@ function getDependencies(context) { peerDependencies: packageContent.peerDependencies || {}, } } catch (e) { + if (packageDir && e.code === 'ENOENT') { + context.report({ + message: 'The package.json file could not be found.', + loc: { line: 0, column: 0 }, + }) + } + if (e.name === 'JSONError' || e instanceof SyntaxError) { + context.report({ + message: 'The package.json file could not be parsed: ' + e.message, + loc: { line: 0, column: 0 }, + }) + } + return null } } @@ -93,6 +110,7 @@ module.exports = { 'devDependencies': { 'type': ['boolean', 'array'] }, 'optionalDependencies': { 'type': ['boolean', 'array'] }, 'peerDependencies': { 'type': ['boolean', 'array'] }, + 'packageDir': { 'type': 'string' }, }, 'additionalProperties': false, }, @@ -102,7 +120,7 @@ module.exports = { create: function (context) { const options = context.options[0] || {} const filename = context.getFilename() - const deps = getDependencies(context) + const deps = getDependencies(context, options.packageDir) if (!deps) { return {} diff --git a/src/rules/no-named-default.js b/src/rules/no-named-default.js index 3185157f3..0625c1f1e 100644 --- a/src/rules/no-named-default.js +++ b/src/rules/no-named-default.js @@ -10,7 +10,7 @@ module.exports = { if (im.type === 'ImportSpecifier' && im.imported.name === 'default') { context.report({ node: im.local, - message: `Use default import syntax to import \'${im.local.name}\'.` }) + message: `Use default import syntax to import '${im.local.name}'.` }) } }) }, diff --git a/src/rules/no-unassigned-import.js b/src/rules/no-unassigned-import.js index a5503f196..2b1499b34 100644 --- a/src/rules/no-unassigned-import.js +++ b/src/rules/no-unassigned-import.js @@ -1,4 +1,6 @@ import isStaticRequire from '../core/staticRequire' +import path from 'path' +import minimatch from 'minimatch' function report(context, node) { context.report({ @@ -7,15 +9,40 @@ function report(context, node) { }) } +function testIsAllow(globs, filename, source) { + if (!Array.isArray(globs)) { + return false // default doesn't allow any patterns + } + + let filePath + + if (source[0] !== '.' && source[0] !== '/') { // a node module + filePath = source + } else { + filePath = path.resolve(path.dirname(filename), source) // get source absolute path + } + + return globs.find(glob => ( + minimatch(filePath, glob) || + minimatch(filePath, path.join(process.cwd(), glob)) + )) !== undefined +} + function create(context) { + const options = context.options[0] || {} + const filename = context.getFilename() + const isAllow = source => testIsAllow(options.allow, filename, source) + return { ImportDeclaration(node) { - if (node.specifiers.length === 0) { + if (node.specifiers.length === 0 && !isAllow(node.source.value)) { report(context, node) } }, ExpressionStatement(node) { - if (node.expression.type === 'CallExpression' && isStaticRequire(node.expression)) { + if (node.expression.type === 'CallExpression' && + isStaticRequire(node.expression) && + !isAllow(node.expression.arguments[0].value)) { report(context, node.expression) } }, @@ -33,6 +60,12 @@ module.exports = { 'devDependencies': { 'type': ['boolean', 'array'] }, 'optionalDependencies': { 'type': ['boolean', 'array'] }, 'peerDependencies': { 'type': ['boolean', 'array'] }, + 'allow': { + 'type': 'array', + 'items': { + 'type': 'string', + }, + }, }, 'additionalProperties': false, }, diff --git a/tests/files/flowtypes.js b/tests/files/flowtypes.js index e4bbb0fd8..7ada3482b 100644 --- a/tests/files/flowtypes.js +++ b/tests/files/flowtypes.js @@ -6,3 +6,7 @@ export type MyType = { firstName: string, lastName: string }; + +export interface MyInterface {} + +export class MyClass {} diff --git a/tests/files/foo-bar-resolver-no-version.js b/tests/files/foo-bar-resolver-no-version.js new file mode 100644 index 000000000..89d4eb13e --- /dev/null +++ b/tests/files/foo-bar-resolver-no-version.js @@ -0,0 +1,14 @@ +var path = require('path') + +exports.resolveImport = function (modulePath, sourceFile, config) { + var sourceFileName = path.basename(sourceFile) + if (sourceFileName === 'foo.js') { + return path.join(__dirname, 'bar.jsx') + } + else if (sourceFileName === 'exception.js') { + throw new Error('foo-bar-resolver-v1 resolveImport test exception') + } + else { + return undefined + } +} diff --git a/tests/files/foo-bar-resolver-v1.js b/tests/files/foo-bar-resolver-v1.js new file mode 100644 index 000000000..8aafe7aa4 --- /dev/null +++ b/tests/files/foo-bar-resolver-v1.js @@ -0,0 +1,16 @@ +var path = require('path') + +exports.resolveImport = function (modulePath, sourceFile, config) { + var sourceFileName = path.basename(sourceFile) + if (sourceFileName === 'foo.js') { + return path.join(__dirname, 'bar.jsx') + } + else if (sourceFileName === 'exception.js') { + throw new Error('foo-bar-resolver-v1 resolveImport test exception') + } + else { + return undefined + } +} + +exports.interfaceVersion = 1 diff --git a/tests/files/foo-bar-resolver-v2.js b/tests/files/foo-bar-resolver-v2.js new file mode 100644 index 000000000..9bb68171b --- /dev/null +++ b/tests/files/foo-bar-resolver-v2.js @@ -0,0 +1,16 @@ +var path = require('path') + +exports.resolve = function (modulePath, sourceFile, config) { + var sourceFileName = path.basename(sourceFile) + if (sourceFileName === 'foo.js') { + return { found: true, path: path.join(__dirname, 'bar.jsx') } + } + else if (sourceFileName === 'exception.js') { + throw new Error('foo-bar-resolver-v2 resolve test exception') + } + else { + return { found: false } + } +} + +exports.interfaceVersion = 2 diff --git a/tests/files/foo-bar-resolver.js b/tests/files/foo-bar-resolver.js deleted file mode 100644 index 92421ba26..000000000 --- a/tests/files/foo-bar-resolver.js +++ /dev/null @@ -1,7 +0,0 @@ -var path = require('path'); - -exports.resolve = function(source, file) { - return { found: true, path: path.join(__dirname, 'bar.jsx') }; -}; - -exports.interfaceVersion = 2; diff --git a/tests/files/ignore.invalid.extension b/tests/files/ignore.invalid.extension new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/with-syntax-error/package.json b/tests/files/with-syntax-error/package.json new file mode 100644 index 000000000..1bb4b63fa --- /dev/null +++ b/tests/files/with-syntax-error/package.json @@ -0,0 +1 @@ +{{ "name": "with-syntax-error" } diff --git a/tests/src/core/getExports.js b/tests/src/core/getExports.js index 62513d442..c3da17fe7 100644 --- a/tests/src/core/getExports.js +++ b/tests/src/core/getExports.js @@ -13,7 +13,7 @@ describe('ExportMap', function () { parserPath: 'babel-eslint', } - it('should handle ExportAllDeclaration', function () { + it('handles ExportAllDeclaration', function () { var imports expect(function () { imports = ExportMap.get('./export-all', fakeContext) @@ -24,12 +24,12 @@ describe('ExportMap', function () { }) - it('should return a cached copy on subsequent requests', function () { + it('returns a cached copy on subsequent requests', function () { expect(ExportMap.get('./named-exports', fakeContext)) .to.exist.and.equal(ExportMap.get('./named-exports', fakeContext)) }) - it('should not return a cached copy after modification', (done) => { + it('does not return a cached copy after modification', (done) => { const firstAccess = ExportMap.get('./mutator', fakeContext) expect(firstAccess).to.exist @@ -42,7 +42,7 @@ describe('ExportMap', function () { }) }) - it('should not return a cached copy with different settings', () => { + it('does not return a cached copy with different settings', () => { const firstAccess = ExportMap.get('./named-exports', fakeContext) expect(firstAccess).to.exist @@ -56,7 +56,7 @@ describe('ExportMap', function () { .not.to.equal(firstAccess) }) - it('should not throw for a missing file', function () { + it('does not throw for a missing file', function () { var imports expect(function () { imports = ExportMap.get('./does-not-exist', fakeContext) @@ -66,7 +66,7 @@ describe('ExportMap', function () { }) - it('should export explicit names for a missing file in exports', function () { + it('exports explicit names for a missing file in exports', function () { var imports expect(function () { imports = ExportMap.get('./exports-missing', fakeContext) diff --git a/tests/src/core/hash.js b/tests/src/core/hash.js new file mode 100644 index 000000000..f8dd4b49a --- /dev/null +++ b/tests/src/core/hash.js @@ -0,0 +1,76 @@ +import { expect } from 'chai' + +import hashify, { hashArray, hashObject } from 'eslint-module-utils/hash' + +const createHash = require('crypto').createHash + +function expectHash(actualHash, expectedString) { + const expectedHash = createHash('sha256') + expectedHash.update(expectedString) + expect(actualHash.digest('hex'), 'to be a hex digest of sha256 hash of string <' + expectedString + '>').to.equal(expectedHash.digest('hex')) +} + +describe('hash', function () { + describe('hashify', function () { + it('handles null', function () { + expectHash(hashify(null), 'null') + }) + + it('handles undefined', function () { + expectHash(hashify(undefined), 'undefined') + }) + + it('handles numbers', function () { + expectHash(hashify(123.456), '123.456') + }) + + it('handles strings', function () { + expectHash(hashify('a string'), '"a string"') + }) + + it('handles Array instances', function () { + expectHash(hashify([ 'a string' ]), '["a string",]') + }) + + it('handles empty Array instances', function () { + expectHash(hashify([]), '[]') + }) + + it('handles Object instances', function () { + expectHash(hashify({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}') + }) + + it('handles nested Object instances', function () { + expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}') + }) + + it('handles nested Object and Array instances', function () { + expectHash(hashify({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}') + }) + }) + + describe('hashArray', function () { + it('handles Array instances', function () { + expectHash(hashArray([ 'a string' ]), '["a string",]') + }) + + it('handles empty Array instances', function () { + expectHash(hashArray([]), '[]') + }) + }) + + describe('hashObject', function () { + it('handles Object instances', function () { + expectHash(hashObject({ foo: 123.456, 'a key': 'a value' }), '{"a key":"a value","foo":123.456,}') + }) + + it('handles nested Object instances', function () { + expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { abc: { def: 'ghi' } } }), '{"a key":"a value","foo":123.456,"obj":{"abc":{"def":"ghi",},},}') + }) + + it('handles nested Object and Array instances', function () { + expectHash(hashObject({ foo: 123.456, 'a key': 'a value', obj: { arr: [ { def: 'ghi' } ] } }), '{"a key":"a value","foo":123.456,"obj":{"arr":[{"def":"ghi",},],},}') + }) + }) + +}) diff --git a/tests/src/core/ignore.js b/tests/src/core/ignore.js new file mode 100644 index 000000000..cc89f8454 --- /dev/null +++ b/tests/src/core/ignore.js @@ -0,0 +1,58 @@ +import { expect } from 'chai' + +import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' + +import * as utils from '../utils' + +describe('ignore', function () { + describe('isIgnored', function () { + it('ignores paths with extensions other than .js', function () { + const testContext = utils.testContext({}) + + expect(isIgnored('../files/foo.js', testContext)).to.equal(false) + + expect(isIgnored('../files/bar.jsx', testContext)).to.equal(true) + + expect(isIgnored('../files/typescript.ts', testContext)).to.equal(true) + + expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true) + }) + + it('ignores paths with invalid extensions when configured with import/extensions', function () { + const testContext = utils.testContext({ 'import/extensions': [ '.js', '.jsx', '.ts' ] }) + + expect(isIgnored('../files/foo.js', testContext)).to.equal(false) + + expect(isIgnored('../files/bar.jsx', testContext)).to.equal(false) + + expect(isIgnored('../files/typescript.ts', testContext)).to.equal(false) + + expect(isIgnored('../files/ignore.invalid.extension', testContext)).to.equal(true) + }) + }) + + describe('hasValidExtension', function () { + it('assumes only .js as valid by default', function () { + const testContext = utils.testContext({}) + + expect(hasValidExtension('../files/foo.js', testContext)).to.equal(true) + + expect(hasValidExtension('../files/foo.jsx', testContext)).to.equal(false) + + expect(hasValidExtension('../files/foo.css', testContext)).to.equal(false) + + expect(hasValidExtension('../files/foo.invalid.extension', testContext)).to.equal(false) + }) + + it('can be configured with import/extensions', function () { + const testContext = utils.testContext({ 'import/extensions': [ '.foo', '.bar' ] }) + + expect(hasValidExtension('../files/foo.foo', testContext)).to.equal(true) + + expect(hasValidExtension('../files/foo.bar', testContext)).to.equal(true) + + expect(hasValidExtension('../files/foo.js', testContext)).to.equal(false) + }) + }) + +}) diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index 5b63910af..dedf43d0d 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -31,6 +31,9 @@ describe('importType(name)', function () { it("should return 'external' for scopes packages", function() { expect(importType('@cycle/core', context)).to.equal('external') expect(importType('@cycle/dom', context)).to.equal('external') + expect(importType('@some-thing/something', context)).to.equal('external') + expect(importType('@some-thing/something/some-module', context)).to.equal('external') + expect(importType('@some-thing/something/some-directory/someModule.js', context)).to.equal('external') }) it("should return 'internal' for non-builtins resolved outside of node_modules", function () { diff --git a/tests/src/core/parse.js b/tests/src/core/parse.js index 0793a70c2..9cc153ae3 100644 --- a/tests/src/core/parse.js +++ b/tests/src/core/parse.js @@ -1,11 +1,14 @@ import * as fs from 'fs' import { expect } from 'chai' +import sinon from 'sinon' import parse from 'eslint-module-utils/parse' import { getFilename } from '../utils' describe('parse(content, { settings, ecmaFeatures })', function () { const path = getFilename('jsx.js') + const parseStubParser = require('./parseStubParser') + const parseStubParserPath = require.resolve('./parseStubParser') let content before((done) => @@ -21,4 +24,37 @@ describe('parse(content, { settings, ecmaFeatures })', function () { .not.to.throw(Error) }) + it('passes expected parserOptions to custom parser', function () { + const parseSpy = sinon.spy() + const parserOptions = { ecmaFeatures: { jsx: true } } + parseStubParser.parse = parseSpy + parse(path, content, { settings: {}, parserPath: parseStubParserPath, parserOptions: parserOptions }) + expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1) + expect(parseSpy.args[0][0], 'custom parser to get content as its first argument').to.equal(content) + expect(parseSpy.args[0][1], 'custom parser to get an object as its second argument').to.be.an('object') + expect(parseSpy.args[0][1], 'custom parser to clone the parserOptions object').to.not.equal(parserOptions) + expect(parseSpy.args[0][1], 'custom parser to get ecmaFeatures in parserOptions which is a clone of ecmaFeatures passed in') + .to.have.property('ecmaFeatures') + .that.is.eql(parserOptions.ecmaFeatures) + .and.is.not.equal(parserOptions.ecmaFeatures) + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.attachComment equal to true').to.have.property('attachComment', true) + expect(parseSpy.args[0][1], 'custom parser to get parserOptions.filePath equal to the full path of the source file').to.have.property('filePath', path) + }) + + it('throws on context == null', function () { + expect(parse.bind(null, path, content, null)).to.throw(Error) + }) + + it('throws on unable to resolve parserPath', function () { + expect(parse.bind(null, path, content, { settings: {}, parserPath: null })).to.throw(Error) + }) + + it('takes the alternate parser specified in settings', function () { + const parseSpy = sinon.spy() + const parserOptions = { ecmaFeatures: { jsx: true } } + parseStubParser.parse = parseSpy + expect(parse.bind(null, path, content, { settings: { 'import/parsers': { [parseStubParserPath]: [ '.js' ] } }, parserPath: null, parserOptions: parserOptions })).not.to.throw(Error) + expect(parseSpy.callCount, 'custom parser to be called once').to.equal(1) + }) + }) diff --git a/tests/src/core/parseStubParser.js b/tests/src/core/parseStubParser.js new file mode 100644 index 000000000..81daace43 --- /dev/null +++ b/tests/src/core/parseStubParser.js @@ -0,0 +1,4 @@ +// this stub must be in a separate file to require from parse via moduleRequire +module.exports = { + parse: function () {}, +} diff --git a/tests/src/core/resolve.js b/tests/src/core/resolve.js index e8f255f34..bfff7935c 100644 --- a/tests/src/core/resolve.js +++ b/tests/src/core/resolve.js @@ -7,24 +7,114 @@ import * as fs from 'fs' import * as utils from '../utils' describe('resolve', function () { - it('should throw on bad parameters.', function () { + it('throws on bad parameters', function () { expect(resolve.bind(null, null, null)).to.throw(Error) }) - it('loads a custom resolver path', function () { - var file = resolve( '../files/foo' - , utils.testContext({ 'import/resolver': './foo-bar-resolver'}) - ) + it('resolves via a custom resolver with interface version 1', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v1' }) - expect(file).to.equal(utils.testFilePath('./bar.jsx')) + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + )).to.equal(undefined) + + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + )).to.equal(undefined) + }) + + it('resolves via a custom resolver with interface version 1 assumed if not specified', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-no-version' }) + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + )).to.equal(undefined) + + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + )).to.equal(undefined) + }) + + it('resolves via a custom resolver with interface version 2', function () { + const testContext = utils.testContext({ 'import/resolver': './foo-bar-resolver-v2' }) + const testContextReports = [] + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo) + } + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + + testContextReports.length = 0 + expect(resolve( '../files/exception' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('exception.js') } }) + )).to.equal(undefined) + expect(testContextReports[0]).to.be.an('object') + expect(testContextReports[0].message).to.equal('Resolve error: foo-bar-resolver-v2 resolve test exception') + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) + + testContextReports.length = 0 + expect(resolve( '../files/not-found' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('not-found.js') } }) + )).to.equal(undefined) + expect(testContextReports.length).to.equal(0) + }) + + it('respects import/resolver as array of strings', function () { + const testContext = utils.testContext({ 'import/resolver': [ './foo-bar-resolver-v2', './foo-bar-resolver-v1' ] }) + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + }) + + it('respects import/resolver as object', function () { + const testContext = utils.testContext({ 'import/resolver': { './foo-bar-resolver-v2': {} } }) + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + }) + + it('respects import/resolver as array of objects', function () { + const testContext = utils.testContext({ 'import/resolver': [ { './foo-bar-resolver-v2': {} }, { './foo-bar-resolver-v1': {} } ] }) + + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(utils.testFilePath('./bar.jsx')) + }) + + it('reports invalid import/resolver config', function () { + const testContext = utils.testContext({ 'import/resolver': 123.456 }) + const testContextReports = [] + testContext.report = function (reportInfo) { + testContextReports.push(reportInfo) + } + + testContextReports.length = 0 + expect(resolve( '../files/foo' + , Object.assign({}, testContext, { getFilename: function () { return utils.getFilename('foo.js') } }) + )).to.equal(undefined) + expect(testContextReports[0]).to.be.an('object') + expect(testContextReports[0].message).to.equal('Resolve error: invalid resolver config') + expect(testContextReports[0].loc).to.eql({ line: 1, column: 0 }) }) it('respects import/resolve extensions', function () { - var file = resolve( './jsx/MyCoolComponent' - , utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) - ) + const testContext = utils.testContext({ 'import/resolve': { 'extensions': ['.jsx'] }}) - expect(file).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')) + expect(resolve( './jsx/MyCoolComponent' + , testContext + )).to.equal(utils.testFilePath('./jsx/MyCoolComponent.jsx')) }) const caseDescribe = (!CASE_SENSITIVE_FS ? describe : describe.skip) diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 5186e56ca..027c5a93d 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -58,8 +58,12 @@ ruleTester.run('default', rule, { // #94: redux export of execution result, test({ code: 'import connectedApp from "./redux"' }), - test({ code: 'import App from "./jsx/App"' - , ecmaFeatures: { jsx: true, modules: true } }), + test({ + code: 'import App from "./jsx/App"', + parserOptions: { + ecmaFeatures: { jsx: true, modules: true }, + }, + }), // from no-errors test({ diff --git a/tests/src/rules/extensions.js b/tests/src/rules/extensions.js index dfc58bfec..3ebd42c18 100644 --- a/tests/src/rules/extensions.js +++ b/tests/src/rules/extensions.js @@ -35,9 +35,9 @@ ruleTester.run('extensions', rule, { test({ code: [ - 'import lib from "./bar"', - 'import lib from "./bar.json"', - 'import lib from "./bar.hbs"', + 'import bar from "./bar"', + 'import barjson from "./bar.json"', + 'import barhbs from "./bar.hbs"', ].join('\n'), options: [ 'always', { js: 'never', jsx: 'never' } ], settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json', '.hbs' ] } }, @@ -45,8 +45,8 @@ ruleTester.run('extensions', rule, { test({ code: [ - 'import lib from "./bar.js"', - 'import lib from "./package"', + 'import bar from "./bar.js"', + 'import pack from "./package"', ].join('\n'), options: [ 'never', { js: 'always', json: 'never' } ], settings: { 'import/resolve': { 'extensions': [ '.js', '.json' ] } }, @@ -145,9 +145,9 @@ ruleTester.run('extensions', rule, { test({ code: [ - 'import lib from "./bar.js"', - 'import lib from "./bar.json"', - 'import lib from "./bar"', + 'import barjs from "./bar.js"', + 'import barjson from "./bar.json"', + 'import barnone from "./bar"', ].join('\n'), options: [ 'always', { json: 'always', js: 'never', jsx: 'never' } ], settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, @@ -155,16 +155,16 @@ ruleTester.run('extensions', rule, { { message: 'Unexpected use of file extension "js" for "./bar.js"', line: 1, - column: 17, + column: 19, }, ], }), test({ code: [ - 'import lib from "./bar.js"', - 'import lib from "./bar.json"', - 'import lib from "./bar"', + 'import barjs from "./bar.js"', + 'import barjson from "./bar.json"', + 'import barnone from "./bar"', ].join('\n'), options: [ 'never', { json: 'always', js: 'never', jsx: 'never' } ], settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } }, @@ -172,7 +172,7 @@ ruleTester.run('extensions', rule, { { message: 'Unexpected use of file extension "js" for "./bar.js"', line: 1, - column: 17, + column: 19, }, ], }), diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index f1c40b474..8cee0c731 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -72,6 +72,14 @@ ruleTester.run('named', rule, { code: 'import type { MyType } from "./flowtypes"', 'parser': 'babel-eslint', }), + test({ + code: 'import type { MyInterface } from "./flowtypes"', + 'parser': 'babel-eslint', + }), + test({ + code: 'import type { MyClass } from "./flowtypes"', + 'parser': 'babel-eslint', + }), // jsnext test({ @@ -135,7 +143,7 @@ ruleTester.run('named', rule, { test({ code: 'import { a } from "./re-export-names"', - args: [2, 'es6-only'], + options: [2, 'es6-only'], errors: [error('a', './re-export-names')], }), diff --git a/tests/src/rules/newline-after-import.js b/tests/src/rules/newline-after-import.js index e5291ba1e..aab38b21b 100644 --- a/tests/src/rules/newline-after-import.js +++ b/tests/src/rules/newline-after-import.js @@ -77,16 +77,16 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parserOptions: { sourceType: 'module' }, }, { - code: `import foo from 'foo';\n\nvar foo = 'bar';`, + code: `import foo from 'foo';\n\nvar bar = 'bar';`, parserOptions: { sourceType: 'module' }, }, { - code: `import foo from 'foo';\n\n\nvar foo = 'bar';`, + code: `import foo from 'foo';\n\n\nvar bar = 'bar';`, parserOptions: { sourceType: 'module' }, options: [{ 'count': 2 }], }, { - code: `import foo from 'foo';\n\n\n\n\nvar foo = 'bar';`, + code: `import foo from 'foo';\n\n\n\n\nvar bar = 'bar';`, parserOptions: { sourceType: 'module' }, options: [{ 'count': 4 }], }, @@ -166,6 +166,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { invalid: [ { code: `import foo from 'foo';\nexport default function() {};`, + output: `import foo from 'foo';\n\nexport default function() {};`, errors: [ { line: 1, column: 1, @@ -175,6 +176,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `import foo from 'foo';\n\nexport default function() {};`, + output: `import foo from 'foo';\n\n\nexport default function() {};`, options: [{ 'count': 2 }], errors: [ { line: 1, @@ -183,8 +185,19 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { } ], parserOptions: { sourceType: 'module' }, }, + { + code: `var foo = require('foo-module');\nvar something = 123;`, + output: `var foo = require('foo-module');\n\nvar something = 123;`, + errors: [ { + line: 1, + column: 1, + message: REQUIRE_ERROR_MESSAGE, + } ], + parserOptions: { sourceType: 'module' }, + }, { code: `import foo from 'foo';\nexport default function() {};`, + output: `import foo from 'foo';\n\nexport default function() {};`, options: [{ 'count': 1 }], errors: [ { line: 1, @@ -195,6 +208,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var foo = require('foo-module');\nvar something = 123;`, + output: `var foo = require('foo-module');\n\nvar something = 123;`, errors: [ { line: 1, column: 1, @@ -204,6 +218,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `import foo from 'foo';\nvar a = 123;\n\nimport { bar } from './bar-lib';\nvar b=456;`, + output: `import foo from 'foo';\n\nvar a = 123;\n\nimport { bar } from './bar-lib';\n\nvar b=456;`, errors: [ { line: 1, @@ -219,6 +234,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var foo = require('foo-module');\nvar a = 123;\n\nvar bar = require('bar-lib');\nvar b=456;`, + output: `var foo = require('foo-module');\n\nvar a = 123;\n\nvar bar = require('bar-lib');\n\nvar b=456;`, errors: [ { line: 1, @@ -234,6 +250,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var foo = require('foo-module');\nvar a = 123;\n\nrequire('bar-lib');\nvar b=456;`, + output: `var foo = require('foo-module');\n\nvar a = 123;\n\nrequire('bar-lib');\n\nvar b=456;`, errors: [ { line: 1, @@ -249,6 +266,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var path = require('path');\nvar foo = require('foo');\nvar bar = 42;`, + output: `var path = require('path');\nvar foo = require('foo');\n\nvar bar = 42;`, errors: [ { line: 2, column: 1, @@ -257,6 +275,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var assign = Object.assign || require('object-assign');\nvar foo = require('foo');\nvar bar = 42;`, + output: `var assign = Object.assign || require('object-assign');\nvar foo = require('foo');\n\nvar bar = 42;`, errors: [ { line: 2, column: 1, @@ -265,6 +284,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `require('a');\nfoo(require('b'), require('c'), require('d'));\nrequire('d');\nvar foo = 'bar';`, + output: `require('a');\nfoo(require('b'), require('c'), require('d'));\nrequire('d');\n\nvar foo = 'bar';`, errors: [ { line: 3, @@ -275,6 +295,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `require('a');\nfoo(\nrequire('b'),\nrequire('c'),\nrequire('d')\n);\nvar foo = 'bar';`, + output: `require('a');\nfoo(\nrequire('b'),\nrequire('c'),\nrequire('d')\n);\n\nvar foo = 'bar';`, errors: [ { line: 6, @@ -285,6 +306,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `import path from 'path';\nimport foo from 'foo';\nvar bar = 42;`, + output: `import path from 'path';\nimport foo from 'foo';\n\nvar bar = 42;`, errors: [ { line: 2, column: 1, @@ -294,6 +316,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `import path from 'path';import foo from 'foo';var bar = 42;`, + output: `import path from 'path';import foo from 'foo';\n\nvar bar = 42;`, errors: [ { line: 1, column: 25, @@ -303,6 +326,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `import foo from 'foo';\n@SomeDecorator(foo)\nclass Foo {}`, + output: `import foo from 'foo';\n\n@SomeDecorator(foo)\nclass Foo {}`, errors: [ { line: 1, column: 1, @@ -313,6 +337,7 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { }, { code: `var foo = require('foo');\n@SomeDecorator(foo)\nclass Foo {}`, + output: `var foo = require('foo');\n\n@SomeDecorator(foo)\nclass Foo {}`, errors: [ { line: 1, column: 1, @@ -322,4 +347,4 @@ ruleTester.run('newline-after-import', require('rules/newline-after-import'), { parser: 'babel-eslint', }, ], -}) +}); diff --git a/tests/src/rules/no-absolute-path.js b/tests/src/rules/no-absolute-path.js index 88951672b..8689997b4 100644 --- a/tests/src/rules/no-absolute-path.js +++ b/tests/src/rules/no-absolute-path.js @@ -26,35 +26,29 @@ ruleTester.run('no-absolute-path', rule, { test({ code: 'var foo = require("foo")'}), test({ code: 'var foo = require("./")'}), test({ code: 'var foo = require("@scope/foo")'}), - test({ - code: 'import events from "events"', - options: [{ - allow: ['events'], - }], - }), + + test({ code: 'import events from "events"' }), + test({ code: 'import path from "path"' }), + test({ code: 'var events = require("events")' }), + test({ code: 'var path = require("path")' }), + test({ code: 'import path from "path";import events from "events"' }), + + // still works if only `amd: true` is provided test({ code: 'import path from "path"', - options: [{ - allow: ['path'], - }], - }), - test({ - code: 'var events = require("events")', - options: [{ - allow: ['events'], - }], + options: [{ amd: true }], }), + + // amd not enabled by default + test({ code: 'require(["/some/path"], function (f) { /* ... */ })' }), + test({ code: 'define(["/some/path"], function (f) { /* ... */ })' }), test({ - code: 'var path = require("path")', - options: [{ - allow: ['path'], - }], + code: 'require(["./some/path"], function (f) { /* ... */ })', + options: [{ amd: true }], }), test({ - code: 'import path from "path";import events from "events"', - options: [{ - allow: ['path', 'events'], - }], + code: 'define(["./some/path"], function (f) { /* ... */ })', + options: [{ amd: true }], }), ], invalid: [ @@ -70,6 +64,11 @@ ruleTester.run('no-absolute-path', rule, { code: 'import f from "/some/path"', errors: [error], }), + test({ + code: 'import f from "/some/path"', + options: [{ amd: true }], + errors: [error], + }), test({ code: 'var f = require("/foo")', errors: [error], @@ -82,5 +81,21 @@ ruleTester.run('no-absolute-path', rule, { code: 'var f = require("/some/path")', errors: [error], }), + test({ + code: 'var f = require("/some/path")', + options: [{ amd: true }], + errors: [error], + }), + // validate amd + test({ + code: 'require(["/some/path"], function (f) { /* ... */ })', + options: [{ amd: true }], + errors: [error], + }), + test({ + code: 'define(["/some/path"], function (f) { /* ... */ })', + options: [{ amd: true }], + errors: [error], + }), ], }) diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 8bb632d01..a2e1955ec 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -1,11 +1,20 @@ import { test } from '../utils' import * as path from 'path' +import * as fs from 'fs' import { RuleTester } from 'eslint' - const ruleTester = new RuleTester() , rule = require('rules/no-extraneous-dependencies') +const packageDirWithSyntaxError = path.join(__dirname, '../../files/with-syntax-error') +const packageFileWithSyntaxErrorMessage = (() => { + try { + JSON.parse(fs.readFileSync(path.join(packageDirWithSyntaxError, 'package.json'))) + } catch (error) { + return error.message + } +})() + ruleTester.run('no-extraneous-dependencies', rule, { valid: [ test({ code: 'import "lodash.cond"'}), @@ -56,6 +65,10 @@ ruleTester.run('no-extraneous-dependencies', rule, { filename: path.join(process.cwd(), 'foo.spec.js'), }), test({ code: 'require(6)' }), + test({ + code: 'import "doctrine"', + options: [{packageDir: path.join(__dirname, '../../../')}], + }), ], invalid: [ test({ @@ -154,5 +167,29 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: '\'lodash.isarray\' should be listed in the project\'s dependencies, not optionalDependencies.', }], }), + test({ + code: 'import "not-a-dependency"', + options: [{packageDir: path.join(__dirname, '../../../')}], + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', + }], + }), + test({ + code: 'import "bar"', + options: [{packageDir: path.join(__dirname, './doesn-exist/')}], + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: 'The package.json file could not be found.', + }], + }), + test({ + code: 'import foo from "foo"', + options: [{packageDir: packageDirWithSyntaxError}], + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage, + }], + }), ], }) diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index 771f5041b..b597f70d5 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -28,6 +28,10 @@ ruleTester.run('no-mutable-exports', rule, { parser: 'babel-eslint', code: 'export Something from "./something";', }), + test({ + parser: 'babel-eslint', + code: 'type Foo = {}\nexport type {Foo}', + }), ], invalid: [ test({ diff --git a/tests/src/rules/no-named-default.js b/tests/src/rules/no-named-default.js index 8ca26f860..b7013fcc7 100644 --- a/tests/src/rules/no-named-default.js +++ b/tests/src/rules/no-named-default.js @@ -13,14 +13,14 @@ ruleTester.run('no-named-default', rule, { ], invalid: [ - test({ + /*test({ code: 'import { default } from "./bar";', errors: [{ message: 'Use default import syntax to import \'default\'.', type: 'Identifier', }], parser: 'babel-eslint', - }), + }),*/ test({ code: 'import { default as bar } from "./bar";', errors: [{ diff --git a/tests/src/rules/no-unassigned-import.js b/tests/src/rules/no-unassigned-import.js index 86248d459..92b276999 100644 --- a/tests/src/rules/no-unassigned-import.js +++ b/tests/src/rules/no-unassigned-import.js @@ -29,6 +29,52 @@ ruleTester.run('no-unassigned-import', rule, { test({ code: 'require("lodash").foo'}), test({ code: 'require("lodash").foo()'}), test({ code: 'require("lodash")()'}), + test({ + code: 'import "app.css"', + options: [{ 'allow': ['**/*.css'] }], + }), + test({ + code: 'import "app.css";', + options: [{ 'allow': ['*.css'] }], + }), + test({ + code: 'import "./app.css"', + options: [{ 'allow': ['**/*.css'] }], + }), + test({ + code: 'import "foo/bar"', + options: [{ 'allow': ['foo/**'] }], + }), + test({ + code: 'import "foo/bar"', + options: [{ 'allow': ['foo/bar'] }], + }), + test({ + code: 'import "../dir/app.css"', + options: [{ 'allow': ['**/*.css'] }], + }), + test({ + code: 'import "../dir/app.js"', + options: [{ 'allow': ['**/dir/**'] }], + }), + test({ + code: 'require("./app.css")', + options: [{ 'allow': ['**/*.css'] }], + }), + test({ + code: 'import "babel-register"', + options: [{ 'allow': ['babel-register'] }], + }), + test({ + code: 'import "./styles/app.css"', + options: [{ 'allow': ['src/styles/**'] }], + filename: path.join(process.cwd(), 'src/app.js'), + }), + test({ + code: 'import "../scripts/register.js"', + options: [{ 'allow': ['src/styles/**', '**/scripts/*.js'] }], + filename: path.join(process.cwd(), 'src/app.js'), + }), ], invalid: [ test({ @@ -39,5 +85,26 @@ ruleTester.run('no-unassigned-import', rule, { code: 'require("lodash")', errors: [error], }), + test({ + code: 'import "./app.css"', + options: [{ 'allow': ['**/*.js'] }], + errors: [error], + }), + test({ + code: 'import "./app.css"', + options: [{ 'allow': ['**/dir/**'] }], + errors: [error], + }), + test({ + code: 'require("./app.css")', + options: [{ 'allow': ['**/*.js'] }], + errors: [error], + }), + test({ + code: 'import "./styles/app.css"', + options: [{ 'allow': ['styles/*.css'] }], + filename: path.join(process.cwd(), 'src/app.js'), + errors: [error], + }), ], }) diff --git a/tests/src/utils.js b/tests/src/utils.js index 144ae5498..144969f5b 100644 --- a/tests/src/utils.js +++ b/tests/src/utils.js @@ -12,11 +12,12 @@ export const FILENAME = testFilePath('foo.js') export function test(t) { return Object.assign({ filename: FILENAME, - parserOptions: { + }, t, { + parserOptions: Object.assign({ sourceType: 'module', ecmaVersion: 6, - }, - }, t) + }, t.parserOptions), + }) } export function testContext(settings) { @@ -87,4 +88,10 @@ export const SYNTAX_CASES = [ test({ code: 'import * as a from "./commonjs-namespace/a"; a.b', }), - ] + + // ignore invalid extensions + test({ + code: 'import { foo } from "./ignore.invalid.extension"', + }), + +] diff --git a/utils/CHANGELOG.md b/utils/CHANGELOG.md index e31196c69..cc9bc051b 100644 --- a/utils/CHANGELOG.md +++ b/utils/CHANGELOG.md @@ -3,9 +3,24 @@ All notable changes to this module will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). -## v2 - 2016-11-07 +## Unreleased + + +## v2.1.1 - 2017-06-22 + +Re-releasing v2.1.0 after vetting (again) and unable to reproduce issue. + + +## v2.1.0 - 2017-06-02 [YANKED] + +Yanked due to critical issue with cache key resulting from #839. + +### Added +- `parse` now additionally passes `filePath` to `parser` in `parserOptions` like `eslint` core does + +## v2.0.0 - 2016-11-07 ### Changed - `unambiguous` no longer exposes fast test regex ### Fixed -- `unambiguous.test()` regex is now properly in multiline mode \ No newline at end of file +- `unambiguous.test()` regex is now properly in multiline mode diff --git a/utils/package.json b/utils/package.json index 0f76e24d9..b961179db 100644 --- a/utils/package.json +++ b/utils/package.json @@ -1,6 +1,6 @@ { "name": "eslint-module-utils", - "version": "2.0.0", + "version": "2.1.1", "description": "Core utilities to support eslint-plugin-import and other module-related plugins.", "engines": { "node": ">=4" @@ -25,7 +25,7 @@ }, "homepage": "https://github.com/benmosher/eslint-plugin-import#readme", "dependencies": { - "debug": "2.2.0", + "debug": "^2.6.8", "pkg-dir": "^1.0.0" } } diff --git a/utils/parse.js b/utils/parse.js index c93417a61..671dc86c0 100644 --- a/utils/parse.js +++ b/utils/parse.js @@ -22,6 +22,10 @@ exports.default = function parse(path, content, context) { // always attach comments parserOptions.attachComment = true + // provide the `filePath` like eslint itself does, in `parserOptions` + // https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637 + parserOptions.filePath = path + // require the parser relative to the main module (i.e., ESLint) const parser = moduleRequire(parserPath)