Skip to content

Commit

Permalink
Add no-anonymous-default-export rule
Browse files Browse the repository at this point in the history
  • Loading branch information
duncanbeevers committed Jan 7, 2017
1 parent c975742 commit 2a12e67
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
65 changes: 65 additions & 0 deletions docs/rules/no-anonymous-default-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# no-anonymous-default-export

Reports if a module's default export is unnamed. This includes several types of unnamed data types; literals, object expressions, arrays, 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.

## Options

By default, all types of anonymous default exports are forbidden, but any types can be selectively allowed by toggling them on in the options.

The complete default configuration looks like this.

```js
"import/no-anonymous-default-export": ["error", {
"allowArray": false,
"allowArrowFunction": false,
"allowAnonymousClass": false,
"allowAnonymousFunction": false,
"allowLiteral": false,
"allowObject": false
}]
```

## Rule Details

### Fail
```js
export default []

export default () => {}

export default class {}

export default function () {}

export default 123

export default {}
```

### Pass
```js
const foo = 123
export default foo

export default function foo() {}

/* eslint import/no-anonymous-default-export: [2, {"allowArray": true}] */
export default []

/* eslint import/no-anonymous-default-export: [2, {"allowArrowFunction": true}] */
export default () => {}

/* eslint import/no-anonymous-default-export: [2, {"allowAnonymousClass": true}] */
export default class {}

/* eslint import/no-anonymous-default-export: [2, {"allowAnonymousFunction": true}] */
export default function () {}

/* eslint import/no-anonymous-default-export: [2, {"allowLiteral": true}] */
export default 123

/* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */
export default {}
```
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
84 changes: 84 additions & 0 deletions src/rules/no-anonymous-default-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @fileoverview Rule to disallow anonymous default exports.
* @author Duncan Beevers
*/

const defs = {
ArrayExpression: {
option: 'allowArray',
message: 'an array',
},
ArrowFunctionExpression: {
option: 'allowArrowFunction',
message: 'an arrow function',
},
ClassDeclaration: {
option: 'allowAnonymousClass',
message: 'an anonymous class',
forbid: (node) => !node.declaration.id,
},
FunctionDeclaration: {
option: 'allowAnonymousFunction',
message: 'an anonymous function',
forbid: (node) => !node.declaration.id,
},
Literal: {
option: 'allowLiteral',
message: 'a literal',
},
ObjectExpression: {
option: 'allowObject',
message: 'an object expression',
},
}

const defaultOptions = Object.keys(defs).
map((key) => defs[key]).
reduce((acc, def) => {
acc[def.options] = false

return acc
})

const schemaProperties = Object.keys(defs).
map((key) => defs[key]).
reduce((acc, def) => {
acc[def.option] = {
description: 'If `false`, will not report default export of ' + def.message,
type: 'boolean',
default: true,
}

return acc
}, {})

module.exports = {
meta: {
schema: [
{
type: 'object',
properties: schemaProperties,
'additionalProperties': false,
},
],
},

create: function (context) {
const options = Object.assign({}, defaultOptions, context.options[0])

return {
'ExportDefaultDeclaration': (node) => {
const def = defs[node.declaration.type]

// Unrecognized node type or else allowed by configuration
if (!def || options[def.option]) {
return
}

if (!def.forbid || def.forbid(node)) {
context.report({ node, message: 'Unexpected default export of ' + def.message })
}
},
}
},
}
47 changes: 47 additions & 0 deletions tests/src/rules/no-anonymous-default-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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: [
// Exports with identifiers are valid
test({ code: 'const foo = 123\nexport default foo' }),
test({ code: 'export default function foo() {}'}),
test({ code: 'export default class MyClass {}'}),

// Allow each forbidden type with appropriate option
test({ code: 'export default []', options: [{ allowArray: true }] }),
test({ code: 'export default () => {}', options: [{ allowArrowFunction: true }] }),
test({ code: 'export default class {}', options: [{ allowAnonymousClass: true }] }),
test({ code: 'export default function() {}', options: [{ allowAnonymousFunction: true }] }),
test({ code: 'export default 123', options: [{ allowLiteral: true }] }),
test({ code: 'export default `foo`', options: [{ allowLiteral: true }] }),
test({ code: 'export default {}', options: [{ allowObject: true }] }),

// Allow forbidden types with multiple options
test({ code: 'export default 123', options: [{ allowLiteral: true, allowObject: true }] }),
test({ code: 'export default {}', options: [{ allowLiteral: true, allowObject: true }] }),

// Sanity check unrelated export syntaxes
test({ code: 'export * from \'foo\'' }),
test({ code: 'const foo = 123\nexport { foo }' }),
test({ code: 'const foo = 123\nexport { foo as default }' }),

...SYNTAX_CASES,
],

invalid: [
test({ code: 'export default []', errors: [{ message: 'Unexpected default export of an array' }] }),
test({ code: 'export default () => {}', errors: [{ message: 'Unexpected default export of an arrow function' }] }),
test({ code: 'export default class {}', errors: [{ message: 'Unexpected default export of an anonymous class' }] }),
test({ code: 'export default 123', errors: [{ message: 'Unexpected default export of a literal' }] }),
test({ code: 'export default {}', errors: [{ message: 'Unexpected default export of an object expression' }] }),
test({ code: 'export default function() {}', errors: [{ message: 'Unexpected default export of an anonymous function' }] }),

// Test failure with non-covering exception
test({ code: 'export default 123', options: [{ allowObject: true }], errors: [{ message: 'Unexpected default export of a literal' }] }),
],
})

0 comments on commit 2a12e67

Please sign in to comment.