From 1e0992d0b65f2674a56f6b64286f6be6666a5def Mon Sep 17 00:00:00 2001 From: "Gavri (Gabriel) Guy" Date: Thu, 5 May 2016 18:48:36 +0300 Subject: [PATCH] Add `prefer-default-export` rule --- CHANGELOG.md | 1 + README.md | 1 + docs/rules/prefer-default-export.md | 51 ++++++++++++++++++++++++ src/index.js | 1 + src/rules/prefer-default-export.js | 31 ++++++++++++++ tests/src/rules/prefer-default-export.js | 48 ++++++++++++++++++++++ 6 files changed, 133 insertions(+) create mode 100644 docs/rules/prefer-default-export.md create mode 100644 src/rules/prefer-default-export.js create mode 100644 tests/src/rules/prefer-default-export.js diff --git a/CHANGELOG.md b/CHANGELOG.md index de46ac24f5..73e4404044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`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]) +- [`prefer-default-export`], new rule. ([#308], thanks [@gavriguy]) ### Fixed - [`extensions`]: fallback to source path for extension enforcement if imported diff --git a/README.md b/README.md index bf530fb995..fc15ac7cf5 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,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/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/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/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.', + }], + }), + ], +})