diff --git a/CHANGELOG.md b/CHANGELOG.md index 82cf35847..21c315388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## [2.8.0] - 2017-10-18 ### Added +- [`no-default-export`] rule ([#889], thanks [@isiahmeadows]) - [`exports-last`] rule ([#620] + [#632], thanks [@k15a]) ### Changed @@ -435,11 +436,13 @@ for info on changes for earlier releases. [`exports-last`]: ./docs/rules/exports-last.md [`group-exports`]: ./docs/rules/group-exports.md [`no-self-import`]: ./docs/rules/no-self-import.md +[`no-default-export`]: ./docs/rules/no-default-export.md [`memo-parser`]: ./memo-parser/README.md [#944]: https://github.com/benmosher/eslint-plugin-import/pull/944 [#891]: https://github.com/benmosher/eslint-plugin-import/pull/891 +[#889]: https://github.com/benmosher/eslint-plugin-import/pull/889 [#858]: https://github.com/benmosher/eslint-plugin-import/pull/858 [#843]: https://github.com/benmosher/eslint-plugin-import/pull/843 [#871]: https://github.com/benmosher/eslint-plugin-import/pull/871 @@ -670,3 +673,4 @@ for info on changes for earlier releases. [@rosswarren]: https://github.com/rosswarren [@alexgorbatchev]: https://github.com/alexgorbatchev [@robertrossmann]: https://github.com/robertrossmann +[@isiahmeadows]: https://github.com/isiahmeadows diff --git a/docs/rules/no-default-export.md b/docs/rules/no-default-export.md new file mode 100644 index 000000000..dc026b00a --- /dev/null +++ b/docs/rules/no-default-export.md @@ -0,0 +1,63 @@ +# no-default-export + +Prohibit default exports. Mostly an inverse of [`prefer-default-export`]. + +[`prefer-default-export`]: ./prefer-default-export.md + +## Rule Details + +The following patterns are considered warnings: + +```javascript +// bad1.js + +// There is a default export. +export const foo = 'foo'; +const bar = 'bar'; +export default 'bar'; +``` + +```javascript +// bad2.js + +// There is a default export. +const foo = 'foo'; +export { foo as default } +``` + +The following patterns are not warnings: + +```javascript +// good1.js + +// There is only a single module export and it's a named export. +export const foo = 'foo'; +``` + +```javascript +// good2.js + +// There is more than one named export in the module. +export const foo = 'foo'; +export const bar = 'bar'; +``` + +```javascript +// good3.js + +// There is more than one named export in the module +const foo = 'foo'; +const bar = 'bar'; +export { foo, bar } +``` + +```javascript +// export-star.js + +// Any batch export will disable this rule. The remote module is not inspected. +export * from './other-module' +``` + +## When Not To Use It + +If you don't care if default imports are used, or if you prefer default imports over named imports. diff --git a/src/index.js b/src/index.js index 57a40ac10..6c5e11252 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,7 @@ export const rules = { 'order': require('./rules/order'), 'newline-after-import': require('./rules/newline-after-import'), 'prefer-default-export': require('./rules/prefer-default-export'), + 'no-default-export': require('./rules/no-default-export'), 'no-dynamic-require': require('./rules/no-dynamic-require'), 'unambiguous': require('./rules/unambiguous'), 'no-unassigned-import': require('./rules/no-unassigned-import'), diff --git a/src/rules/no-default-export.js b/src/rules/no-default-export.js new file mode 100644 index 000000000..8d240ed6a --- /dev/null +++ b/src/rules/no-default-export.js @@ -0,0 +1,35 @@ +module.exports = { + meta: { + docs: {}, + }, + + create(context) { + // ignore non-modules + if (context.parserOptions.sourceType !== 'module') { + return {} + } + + const preferNamed = 'Prefer named exports.' + const noAliasDefault = ({local}) => + `Do not alias \`${local.name}\` as \`default\`. Just export ` + + `\`${local.name}\` itself instead.` + + return { + ExportDefaultDeclaration(node) { + context.report({node, message: preferNamed}) + }, + + ExportNamedDeclaration(node) { + node.specifiers.forEach(specifier => { + if (specifier.type === 'ExportDefaultSpecifier' && + specifier.exported.name === 'default') { + context.report({node, message: preferNamed}) + } else if (specifier.type === 'ExportSpecifier' && + specifier.exported.name === 'default') { + context.report({node, message: noAliasDefault(specifier)}) + } + }) + }, + } + }, +} diff --git a/tests/src/rules/no-default-export.js b/tests/src/rules/no-default-export.js new file mode 100644 index 000000000..6440bfa89 --- /dev/null +++ b/tests/src/rules/no-default-export.js @@ -0,0 +1,121 @@ +import { test } from '../utils' + +import { RuleTester } from 'eslint' + +const ruleTester = new RuleTester() + , rule = require('rules/no-default-export') + +ruleTester.run('no-default-export', rule, { + valid: [ + test({ + code: ` + export const foo = 'foo'; + export const bar = 'bar'; + `, + }), + test({ + code: ` + export const foo = 'foo'; + export function bar() {}; + `, + }), + test({ + code: `export const foo = 'foo';`, + }), + test({ + code: ` + const foo = 'foo'; + export { foo }; + `, + }), + test({ + code: `export { foo, bar }`, + }), + test({ + code: `export const { foo, bar } = item;`, + }), + test({ + code: `export const { foo, bar: baz } = item;`, + }), + test({ + code: `export const { foo: { bar, baz } } = item;`, + }), + test({ + code: ` + export const foo = item; + export { item }; + `, + }), + test({ + code: `export * from './foo';`, + }), + test({ + code: `export const { foo } = { foo: "bar" };`, + }), + test({ + code: `export const { foo: { bar } } = { foo: { bar: "baz" } };`, + }), + test({ + code: 'export { a, b } from "foo.js"', + parser: 'babel-eslint', + }), + + // no exports at all + test({ + code: `import * as foo from './foo';`, + }), + test({ + code: `import foo from './foo';`, + }), + test({ + code: `import {default as foo} from './foo';`, + }), + + test({ + code: `export type UserId = number;`, + parser: 'babel-eslint', + }), + test({ + code: 'export foo from "foo.js"', + parser: 'babel-eslint', + }), + test({ + code: `export Memory, { MemoryValue } from './Memory'`, + parser: 'babel-eslint', + }), + ], + invalid: [ + test({ + code: 'export default function bar() {};', + errors: [{ + ruleId: 'ExportDefaultDeclaration', + message: 'Prefer named exports.', + }], + }), + test({ + code: ` + export const foo = 'foo'; + export default bar;`, + errors: [{ + ruleId: 'ExportDefaultDeclaration', + message: 'Prefer named exports.', + }], + }), + test({ + code: 'export { foo as default }', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Do not alias `foo` as `default`. Just export `foo` itself ' + + 'instead.', + }], + }), + test({ + code: 'export default from "foo.js"', + parser: 'babel-eslint', + errors: [{ + ruleId: 'ExportNamedDeclaration', + message: 'Prefer named exports.', + }], + }), + ], +})