diff --git a/CHANGELOG.md b/CHANGELOG.md index ead5df2889..46699d7ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project 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). +## [1.8.0] - 2016-05-11 +### Added +- [`prefer-default-export`], new rule. ([#308], thanks [@gavriguy]) + +### Fixed +- Ignore namespace / ES7 re-exports in [`no-mutable-exports`]. ([#317], fixed by [#322]. thanks [@borisyankov] + [@jfmengels]) +- Make [`no-extraneous-dependencies`] handle scoped packages ([#316], thanks [@jfmengels]) + ## [1.7.0] - 2016-05-06 ### Added - [`newline-after-import`], new rule. ([#245], thanks [@singles]) @@ -11,7 +19,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - add [`no-mutable-exports`] rule ([#290], thanks [@josh]) - [`import/extensions` setting]: a whitelist of file extensions to parse as modules and search for `export`s. If unspecified, all extensions are considered valid (for now). - In v2, this will likely default to `['.js', MODULE_EXT]`,. ([#297], to fix [#267]) + In v2, this will likely default to `['.js', MODULE_EXT]`. ([#297], to fix [#267]) ### Fixed - [`extensions`]: fallback to source path for extension enforcement if imported @@ -200,7 +208,11 @@ for info on changes for earlier releases. [`named`]: ./docs/rules/named.md [`newline-after-import`]: ./docs/rules/newline-after-import.md [`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md +[`prefer-default-export`]: ./docs/rules/prefer-default-export.md +[#322]: https://github.com/benmosher/eslint-plugin-import/pull/322 +[#316]: https://github.com/benmosher/eslint-plugin-import/pull/316 +[#308]: https://github.com/benmosher/eslint-plugin-import/pull/308 [#298]: https://github.com/benmosher/eslint-plugin-import/pull/298 [#297]: https://github.com/benmosher/eslint-plugin-import/pull/297 [#296]: https://github.com/benmosher/eslint-plugin-import/pull/296 @@ -223,6 +235,7 @@ for info on changes for earlier releases. [#164]: https://github.com/benmosher/eslint-plugin-import/pull/164 [#157]: https://github.com/benmosher/eslint-plugin-import/pull/157 +[#317]: https://github.com/benmosher/eslint-plugin-import/issues/317 [#286]: https://github.com/benmosher/eslint-plugin-import/issues/286 [#281]: https://github.com/benmosher/eslint-plugin-import/issues/281 [#272]: https://github.com/benmosher/eslint-plugin-import/issues/272 @@ -239,7 +252,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/v1.7.0...HEAD +[Unreleased]: https://github.com/benmosher/eslint-plugin-import/compare/v1.8.0...HEAD +[1.8.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.7.0...v1.8.0 [1.7.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.6.1...v1.7.0 [1.6.1]: https://github.com/benmosher/eslint-plugin-import/compare/v1.6.0...v1.6.1 [1.6.0]: https://github.com/benmosher/eslint-plugin-import/compare/v1.5.0...1.6.0 @@ -274,3 +288,5 @@ for info on changes for earlier releases. [@strawbrary]: https://github.com/strawbrary [@SimenB]: https://github.com/SimenB [@josh]: https://github.com/josh +[@borisyankov]: https://github.com/borisyankov +[@gavriguy]: https://github.com/gavriguy diff --git a/README.md b/README.md index bf530fb995..37bdd861e7 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Ensure consistent use of file extension within the import path ([`extensions`]) * Enforce a convention in module import order ([`order`]) * Enforce a newline after import statements ([`newline-after-import`]) +* Prefer a default export if module exports a single name ([`prefer-default-export`]) [`imports-first`]: ./docs/rules/imports-first.md [`no-duplicates`]: ./docs/rules/no-duplicates.md @@ -64,6 +65,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`extensions`]: ./docs/rules/extensions.md [`order`]: ./docs/rules/order.md [`newline-after-import`]: ./docs/rules/newline-after-import.md +[`prefer-default-export`]: ./docs/rules/prefer-default-export.md ## Installation diff --git a/docs/rules/prefer-default-export.md b/docs/rules/prefer-default-export.md new file mode 100644 index 0000000000..892abfa38a --- /dev/null +++ b/docs/rules/prefer-default-export.md @@ -0,0 +1,51 @@ +# prefer-default-export + +When there is only a single export from a module prefer using default export over named export. + +## Rule Details + +The following patterns are considered warnings: + +```javascript +// bad.js + +// There is only a single module export and its a named export. +export const foo = 'foo'; + +``` + +The following patterns are not warnings: + +```javascript +// good1.js + +// There is a default export. +export const foo = 'foo'; +const bar = 'bar'; +export default 'bar'; +``` + +```javascript +// good2.js + +// There is more thank one named exports in the module. +export const foo = 'foo'; +export const bar = 'bar'; +``` + +```javascript +// good3.js + +// There is more thank one named exports in the module +const foo = 'foo'; +const bar = 'bar'; +export { foo, bar } +``` + +```javascript +// good4.js + +// There is a default export. +const foo = 'foo'; +export { foo as default } +``` diff --git a/package.json b/package.json index 5492aff554..774a5a5118 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-import", - "version": "1.7.0", + "version": "1.8.0", "description": "Import with sanity.", "main": "lib/index.js", "directories": { diff --git a/src/index.js b/src/index.js index 0d1eb15cfa..f9da72a223 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,7 @@ export const rules = { 'no-nodejs-modules': require('./rules/no-nodejs-modules'), 'order': require('./rules/order'), 'newline-after-import': require('./rules/newline-after-import'), + 'prefer-default-export': require('./rules/prefer-default-export'), // metadata-based 'no-deprecated': require('./rules/no-deprecated'), diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 8f571fe2a0..becbad0926 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -39,7 +39,10 @@ function reportIfMissing(context, deps, allowDevDeps, allowOptDeps, node, name) if (importType(name, context) !== 'external') { return } - const packageName = name.split('/')[0] + const splitName = name.split('/') + const packageName = splitName[0][0] === '@' + ? splitName.slice(0, 2).join('/') + : splitName[0] const isInDeps = deps.dependencies[packageName] !== undefined const isInDevDeps = deps.devDependencies[packageName] !== undefined diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index ba403d7155..e6173bf11c 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -31,7 +31,7 @@ module.exports = function (context) { if (node.declaration) { checkDeclaration(node.declaration) - } else { + } else if (!node.source) { for (let specifier of node.specifiers) { checkDeclarationsInScope(scope, specifier.local.name) } diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js new file mode 100644 index 0000000000..871cd8187c --- /dev/null +++ b/src/rules/prefer-default-export.js @@ -0,0 +1,31 @@ +'use strict' + +module.exports = function(context) { + let namedExportCount = 0 + let specifierExportCount = 0 + let hasDefaultExport = false + let namedExportNode = null + return { + 'ExportSpecifier': function(node) { + if (node.exported.name === 'default') { + hasDefaultExport = true + } else { + specifierExportCount++ + namedExportNode = node + } + }, + 'ExportNamedDeclaration': function(node) { + namedExportCount++ + namedExportNode = node + }, + 'ExportDefaultDeclaration': function() { + hasDefaultExport = true + }, + + 'Program:exit': function() { + if (namedExportCount === 1 && specifierExportCount < 2 && !hasDefaultExport) { + context.report(namedExportNode, 'Prefer default export.') + } + }, + } +} diff --git a/tests/files/package.json b/tests/files/package.json index 3e2bd0fa09..d0ef9601b4 100644 --- a/tests/files/package.json +++ b/tests/files/package.json @@ -7,6 +7,7 @@ "eslint": "2.x" }, "dependencies": { + "@scope/core": "^1.0.0", "lodash.cond": "^4.3.0", "pkg-up": "^1.0.0" }, diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 7335e1fd36..e849880094 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -21,11 +21,12 @@ ruleTester.run('no-extraneous-dependencies', rule, { test({ code: 'import "fs"'}), test({ code: 'import "./foo"'}), test({ code: 'import "lodash.isarray"'}), + test({ code: 'import "@scope/core"'}), // 'project' type test({ code: 'import "importType"', - settings: { "import/resolver": { node: { paths: [ path.join(__dirname, '../../files') ] } } }, + settings: { 'import/resolver': { node: { paths: [ path.join(__dirname, '../../files') ] } } }, }), ], invalid: [ @@ -36,6 +37,20 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', }], }), + test({ + code: 'var donthaveit = require("@scope/donthaveit")', + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: '\'@scope/donthaveit\' should be listed in the project\'s dependencies. Run \'npm i -S @scope/donthaveit\' to add it', + }], + }), + test({ + code: 'var donthaveit = require("@scope/donthaveit/lib/foo")', + errors: [{ + ruleId: 'no-extraneous-dependencies', + message: '\'@scope/donthaveit\' should be listed in the project\'s dependencies. Run \'npm i -S @scope/donthaveit\' to add it', + }], + }), test({ code: 'import "eslint"', options: [{devDependencies: false}], diff --git a/tests/src/rules/no-mutable-exports.js b/tests/src/rules/no-mutable-exports.js index 14f1ef7c77..d3874b7972 100644 --- a/tests/src/rules/no-mutable-exports.js +++ b/tests/src/rules/no-mutable-exports.js @@ -24,6 +24,10 @@ ruleTester.run('no-mutable-exports', rule, { test({ code: 'class Counter {}\nexport { Counter as Count }'}), test({ code: 'class Counter {}\nexport default Counter'}), test({ code: 'class Counter {}\nexport { Counter as default }'}), + test({ + parser: 'babel-eslint', + code: 'export Something from "./something";', + }), ], invalid: [ test({ diff --git a/tests/src/rules/prefer-default-export.js b/tests/src/rules/prefer-default-export.js new file mode 100644 index 0000000000..c3827eeba2 --- /dev/null +++ b/tests/src/rules/prefer-default-export.js @@ -0,0 +1,48 @@ +import { test } from '../utils' + +import { RuleTester } from 'eslint' + +const ruleTester = new RuleTester() + , rule = require('rules/prefer-default-export') + +ruleTester.run('prefer-default-export', rule, { + valid: [ + test({ + code: ` + export const foo = 'foo'; + export const bar = 'bar';`, + }), + test({ + code: ` + export const foo = 'foo'; + export default bar;`, + }), + test({ + code: ` + export { foo, bar }`, + }), + test({ + code: ` + export { foo as default }`, + }), + ], + invalid: [ + test({ + code: ` + export const foo = 'foo';`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Prefer default export.', + }], + }), + test({ + code: ` + const foo = 'foo'; + export { foo };`, + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Prefer default export.', + }], + }), + ], +})