diff --git a/CHANGELOG.md b/CHANGELOG.md index e1ff0c4e70..613a7608ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com). ## [Unreleased] +### Added +- [`no-anonymous-default-export`] rule: report anonymous default exports. + ### Changed - [`no-extraneous-dependencies`]: use `read-pkg-up` to simplify finding + loading `package.json` ([#680], thanks [@wtgtybhertgeghgtwtg]) diff --git a/README.md b/README.md index 519e707d81..c8c3789aa8 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Limit the maximum number of dependencies a module can have ([`max-dependencies`]) * Forbid unassigned imports ([`no-unassigned-import`]) * Forbid named default exports ([`no-named-default`]) +* Forbid anonymous values as default exports ([`no-anonymous-default-export`]) [`first`]: ./docs/rules/first.md [`no-duplicates`]: ./docs/rules/no-duplicates.md @@ -87,6 +88,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`max-dependencies`]: ./docs/rules/max-dependencies.md [`no-unassigned-import`]: ./docs/rules/no-unassigned-import.md [`no-named-default`]: ./docs/rules/no-named-default.md +[`no-anonymous-default-export`]: ./docs/rules/no-anonymous-default-export.md ## Installation diff --git a/docs/rules/no-anonymous-default-export.md b/docs/rules/no-anonymous-default-export.md new file mode 100644 index 0000000000..eb1ca9f67b --- /dev/null +++ b/docs/rules/no-anonymous-default-export.md @@ -0,0 +1,45 @@ +# no-anonymous-default-export + +Reports if a module's default export is unnamed. This includes several types of unnamed data types; literals, object expressions, anonymous functions, arrow functions, and anonymous class declarations. + +Ensuring that default exports are named helps improve the grepability of the codebase by encouraging the re-use of the same identifier for the module's default export at its declaration site and at its import sites. + +By default, all types of anonymous default exports are forbidden, but each type can be selectively allowed using options. + +## Rule Details + +### Fail +```js +export default 123 + +export default {} + +export default function () {} + +export default () => {} + +export default class {} +``` + +### Pass +```js +const foo = 123 +export default foo + +export default function foo() {} + +/*eslint no-anonymous-default-export: [2, "allow-literal"]*/ +export default 123 + +/*eslint no-anonymous-default-export: [2, "object-expression"]*/ +export default {} + +/*eslint no-anonymous-default-export: [2, "allow-anonymous-function-declaration"]*/ +export default function () {} + +/*eslint no-anonymous-default-export: [2, "allow-arrow-function-expression"]*/ +export default () => {} + +/*eslint no-anonymous-default-export: [2, "allow-anonymous-class-declaration"]*/ +export default class {} +``` diff --git a/src/index.js b/src/index.js index 67fdb13261..69cbc2f5e6 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ export const rules = { 'no-named-default': require('./rules/no-named-default'), 'no-named-as-default': require('./rules/no-named-as-default'), 'no-named-as-default-member': require('./rules/no-named-as-default-member'), + 'no-anonymous-default-export': require('./rules/no-anonymous-default-export'), 'no-commonjs': require('./rules/no-commonjs'), 'no-amd': require('./rules/no-amd'), diff --git a/src/rules/no-anonymous-default-export.js b/src/rules/no-anonymous-default-export.js new file mode 100644 index 0000000000..28c4e2033c --- /dev/null +++ b/src/rules/no-anonymous-default-export.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Rule to disallow anonymous default exports. + * @author Duncan Beevers + */ + +module.exports = { + meta: {}, + + create: function (context) { + + const forbid = (option) => !~context.options.indexOf('allow-' + option) + + return { + 'ExportDefaultDeclaration': (node) => { + const type = node.declaration.type + const noID = !node.declaration.id + + if (type === 'Literal' && forbid('literal')) { + context.report({ + node: node, + message: 'Unexpected default export of literal', + }) + return + } + + if (type === 'ObjectExpression' && forbid('object-expression')) { + context.report({ + node: node, + message: 'Unexpected default export of object expression', + }) + return + } + + if (type === 'FunctionDeclaration' && noID && forbid('anonymous-function-declaration')) { + context.report({ + node: node, + message: 'Unexpected default export of anonymous function', + }) + return + } + + if (type === 'ArrowFunctionExpression' && forbid('arrow-function-expression')) { + context.report({ + node: node, + message: 'Unexpected default export of arrow function', + }) + return + } + + if (type === 'ClassDeclaration' && noID && forbid('anonymous-class-declaration')) { + context.report({ + node: node, + message: 'Unexpected default export of anonymous class', + }) + return + } + }, + } + + }, +} diff --git a/tests/src/rules/no-anonymous-default-export.js b/tests/src/rules/no-anonymous-default-export.js new file mode 100644 index 0000000000..32c9fc12be --- /dev/null +++ b/tests/src/rules/no-anonymous-default-export.js @@ -0,0 +1,30 @@ +import { test, SYNTAX_CASES } from '../utils' + +import { RuleTester } from 'eslint' + +var ruleTester = new RuleTester() +var rule = require('rules/no-anonymous-default-export') + +ruleTester.run('no-anonymous-default-export', rule, { + valid: [ + test({ code: 'const foo = 123\nexport default foo' }), + test({ code: 'export default function foo() {}'}), + test({ code: 'export default class MyClass {}'}), + + test({ code: 'export default 123', options: ['allow-literal'] }), + test({ code: 'export default {}', options: ['allow-object-expression'] }), + test({ code: 'export default class {}', options: ['allow-anonymous-class-declaration'] }), + test({ code: 'export default function() {}', options: ['allow-anonymous-function-declaration'] }), + test({ code: 'export default () => {}', options: ['allow-arrow-function-expression'] }), + + ...SYNTAX_CASES, + ], + + invalid: [ + test({ code: 'export default 123', errors: [{ message: 'Unexpected default export of literal' }] }), + test({ code: 'export default {}', errors: [{ message: 'Unexpected default export of object expression' }] }), + test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of anonymous class' }] }), + test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of anonymous function' }] }), + test({ code: 'export default () => {}', errors: [{ message: 'Unexpected default export of arrow function' }] }), + ], +})